diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/D.sln b/D.sln new file mode 100644 index 0000000..f98b65a --- /dev/null +++ b/D.sln @@ -0,0 +1,73 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26403.7 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Darkstar", "D\Darkstar.csproj", "{0590465E-1D91-4591-946E-EE3F7D82834B}" +EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "DarkstarSetup", "DSetup\DarkstarSetup.wixproj", "{BCB1273C-6A17-4AFE-88AD-470A3594A009}" +EndProject +Global + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ia64 = Debug|ia64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ia64 = Release|ia64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|ia64.ActiveCfg = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|ia64.Build.0 = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|x64.ActiveCfg = Debug|x64 + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|x64.Build.0 = Debug|x64 + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Debug|x86.Build.0 = Debug|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|Any CPU.Build.0 = Release|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|ia64.ActiveCfg = Release|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|ia64.Build.0 = Release|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|x64.ActiveCfg = Release|x64 + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|x64.Build.0 = Release|x64 + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|x86.ActiveCfg = Release|Any CPU + {0590465E-1D91-4591-946E-EE3F7D82834B}.Release|x86.Build.0 = Release|Any CPU + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Debug|Any CPU.ActiveCfg = Debug|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Debug|ia64.ActiveCfg = Debug|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Debug|x64.ActiveCfg = Debug|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Debug|x86.ActiveCfg = Debug|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Debug|x86.Build.0 = Debug|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Release|Any CPU.ActiveCfg = Release|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Release|ia64.ActiveCfg = Release|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Release|x64.ActiveCfg = Release|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Release|x86.ActiveCfg = Release|x86 + {BCB1273C-6A17-4AFE-88AD-470A3594A009}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DA616BB8-1CC8-43E2-A841-6DB3E9DA4AA8} + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection + GlobalSection(Performance) = preSolution + HasPerformanceSessions = true + EndGlobalSection +EndGlobal diff --git a/D/App.config b/D/App.config new file mode 100644 index 0000000..2b933e2 --- /dev/null +++ b/D/App.config @@ -0,0 +1,48 @@ + + + + +
+ + + + + + + + + + + + + + + True + + + 1 + + + 2852201285 + + + + + + 1024 + + + True + + + 1979-12-10 + + + 0 + + + 1955-11-05 + + + + \ No newline at end of file diff --git a/D/CP/AM2901.cs b/D/CP/AM2901.cs new file mode 100644 index 0000000..922129b --- /dev/null +++ b/D/CP/AM2901.cs @@ -0,0 +1,807 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; + +namespace D.CP +{ + /// + /// This implements an abstraction of the AMD 2901 as seen by the Central Processor -- + /// that is: as a 16-bit ALU + register file, rather than four 4-bit ALUs hooked together. + /// This only implements the signals and operations that the CP actually cares about. + /// + public class AM2901 + { + static AM2901() + { + BuildTables(); + } + + public AM2901() + { + + } + + public ushort[] R + { + get { return _r; } + } + + public ushort Q + { + get { return _q; } + } + + /// + /// Executes the ALU operation specified by the given microinstruction. + /// + /// The microinstruction + /// The ALU D input + /// The ALU Carry in + /// Whether this operation is taking place during an MAR<- operation, + /// in which case the top half of the ALU needs to be treated specially. + public ushort Execute(Microinstruction i, ushort d, bool carryIn, bool loadMAR) + { + // + // Save R[a] for the A-bypass case. + // (If the ALU op ends up modifying R[a] in A-Bypass mode (because a == b) + // it will happen much later than A-bypass and we want + // Y to get the original value of R[a], not the later value.) + // + + // Select source data + int r, s; + switch(i.aS) + { + case AluSourcePair.AQ: + r = _r[i.rA]; + s = _q; + break; + + case AluSourcePair.AB: + r = _r[i.rA]; + s = _r[i.rB]; + break; + + case AluSourcePair.ZQ: + r = 0; + s = _q; + break; + + case AluSourcePair.ZB: + r = 0; + s = _r[i.rB]; + break; + + case AluSourcePair.ZA: + r = 0; + s = _r[i.rA]; + break; + + case AluSourcePair.DA: + r = d; + s = _r[i.rA]; + break; + + case AluSourcePair.DQ: + r = d; + s = _q; + break; + + case AluSourcePair.D0: + r = d; + s = 0; + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled source pair {0}", i.aS)); + } + + // + // Do ALU op + // + int f; + int cIn = (carryIn ? 1 : 0); + switch (i.aF) + { + case AluFunction.RplusS: + { + f = r + s + cIn; + CarryOut = (f > 0xffff); + NibCarry = (r & 0xf) + (s & 0xf) + cIn > 0xf; + PgCarry = (r & 0xff) + (s & 0xff) + cIn > 0xff; + int cn = (r & 0xfff) + (s & 0xfff) + cIn > 0xfff ? 1 : 0; + Overflow = _overflowTable[r >> 12, s >> 12, cn]; + } + break; + + case AluFunction.SminusR: + { + f = s + (~r & 0xffff) + cIn; + CarryOut = (f > 0xffff); + NibCarry = ((~r & 0xf) + (s & 0xf) + cIn > 0xf); + PgCarry = ((~r & 0xff) + (s & 0xff) + cIn > 0xff); + int cn = (~r & 0xfff) + (s & 0xfff) + cIn > 0xfff ? 1 : 0; + Overflow = _overflowTable[(~r & 0xffff) >> 12, s >> 12, cn]; + } + break; + + case AluFunction.RminusS: + { + f = r + (~s & 0xffff) + cIn; + CarryOut = (f > 0xffff); + NibCarry = ((r & 0xf) + (~s & 0xf) + cIn > 0xf); + PgCarry = ((r & 0xff) + (~s & 0xff) + cIn > 0xff); + int cn = (r & 0xfff) + (~s & 0xfff) + cIn > 0xfff ? 1 : 0; + Overflow = _overflowTable[r >> 12, (~s & 0xffff) >> 12, cn]; + } + break; + + case AluFunction.RorS: + f = r | s; + // A few microinstructions do an MAR<- with RorS and expect PgCarry to be set appropriately. + NibCarry = _carryTableOr[r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableOr[(r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + CarryOut = false; + Overflow = false; + break; + + case AluFunction.RandS: + f = r & s; + NibCarry = false; + PgCarry = false; + CarryOut = false; + Overflow = false; + break; + + case AluFunction.notRandS: + f = (~r) & s; + NibCarry = false; + PgCarry = false; + CarryOut = false; + Overflow = false; + break; + + case AluFunction.RxorS: + f = r ^ s; + NibCarry = false; + PgCarry = false; + CarryOut = false; + Overflow = false; + break; + + case AluFunction.notRxorS: + f = (~r) ^ s; + NibCarry = false; + PgCarry = false; + CarryOut = false; + Overflow = false; + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled function {0}", i.aF)); + } + + // Clip F to 16 bits + f = f & 0xffff; + + if (loadMAR) + { + // + // If the ALU is being run during a MAR<- operation, the top 8 bits of the ALU are + // computed using an operator specified by aF | 3, with the source set to 0,B. + // The CarryOut and Overflow flags are clear (since they are not affected by the + // OR/notXOR operation), and the carry from the least-significant byte of the ALU does not + // carry over to the most-significant byte. + // + // See page 25 of the microcode ref for details. + // + // We implement this here by overwriting the upper byte of F with the upper bits of rB + // (or its complement). Interlisp microcode expects Overflow and Carry to be set appropriately. + // + switch ((AluFunction)((int)i.aF | 0x3)) + { + case AluFunction.RorS: + { + f = (f & 0xff) | (_r[i.rB] & 0xff00); + bool midCarry = _carryTableOr[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + Overflow = CarryOut = _carryTableOr[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.notRxorS: + { + f = (f & 0xff) | ((~_r[i.rB]) & 0xff00); + bool midCarry = _carryTableNotXor[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + } + } + + Zero = (f == 0); + Neg = ((f & 0x8000) != 0); + + // + // Write outputs, do shifts and cycles as appropriate before writing back. + // (Shifts and cycles do not affect the Y output, only the register being written back to.) + // + switch (i.AluDestination) + { + case 0: + _q = (ushort)f; + Y = (ushort)f; + break; + + case 1: + Y = (ushort)f; + break; + + case 2: + Y = _r[i.rA]; + _r[i.rB] = (ushort)f; + break; + + case 3: + _r[i.rB] = (ushort)f; + Y = (ushort)f; + break; + + case 4: + Y = (ushort)f; + + if (i.Cycle) + { + // double-word right shift + // MSB of Q gets inverted LSB of F. + _q = (ushort)((_q >> 1) | ((~f & 0x1) << 15)); + + // MSB of F gets Carry in. + f = (ushort)((f >> 1) | (carryIn ? 0x8000 : 0x0)); + } + else + { + // double-word arithmetic right shift. + // MSB of Q gets inverted LSB of F. + _q = (ushort)((_q >> 1) | ((~f & 0x1) << 15)); + + // MSB of F gets Carry out. + f = (ushort)((f >> 1) | (CarryOut ? 0x8000 : 0x0)); + } + _r[i.rB] = (ushort)f; + break; + + case 5: + Y = (ushort)f; + if (i.Cycle) + { + // F: single-word right rotate: + f = (ushort)((f >> 1) | ((f & 0x1) << 15)); + } + else + { + // F: single-word right shift w/carryIn to MSB: + f = (ushort)((f >> 1) | (carryIn ? 0x8000 : 0x0)); + } + _r[i.rB] = (ushort)f; + break; + + case 6: + Y = (ushort)f; + + // double-word left shift (apparently identical for cycle and shift) + // LSB of F gets MSB of Q, not inverted. + f = (ushort)((f << 1) | ((_q & 0x8000) >> 15)); + + // LSB of Q gets Cin, inverted + _q = (ushort)((_q << 1) | (1 - cIn)); + + _r[i.rB] = (ushort)f; + break; + + case 7: + Y = (ushort)f; + if (i.Cycle) + { + // F: single-word left rotate: + f = (ushort)((f << 1) | ((f & 0x8000) >> 15)); + } + else + { + // F: single-word left shift w/carryIn to LSB: + f = (ushort)((f << 1) | cIn); + } + _r[i.rB] = (ushort)f; + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled destination {0}", i.aF)); + } + + return Y; + } + + /// + /// Executes the ALU operation specified by the given microinstruction with all condition flags + /// calculated, even for logical operations. This is significantly slower than Execute(). + /// + /// The microinstruction + /// The ALU D input + /// The ALU Carry in + /// Whether this operation is taking place during an MAR<- operation, + /// in which case the top half of the ALU needs to be treated specially. + public ushort ExecuteAccurate(Microinstruction i, ushort d, bool carryIn, bool loadMAR) + { + // Select source data + int r, s; + switch (i.aS) + { + case AluSourcePair.AQ: + r = _r[i.rA]; + s = _q; + break; + + case AluSourcePair.AB: + r = _r[i.rA]; + s = _r[i.rB]; + break; + + case AluSourcePair.ZQ: + r = 0; + s = _q; + break; + + case AluSourcePair.ZB: + r = 0; + s = _r[i.rB]; + break; + + case AluSourcePair.ZA: + r = 0; + s = _r[i.rA]; + break; + + case AluSourcePair.DA: + r = d; + s = _r[i.rA]; + break; + + case AluSourcePair.DQ: + r = d; + s = _q; + break; + + case AluSourcePair.D0: + r = d; + s = 0; + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled source pair {0}", i.aS)); + } + + // + // Do ALU op + // + int f; + int cIn = (carryIn ? 1 : 0); + switch (i.aF) + { + case AluFunction.RplusS: + { + f = r + s + cIn; + NibCarry = _carryTableArithmetic[r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableArithmetic[(r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableArithmetic[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableArithmetic[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowTable[r >> 12, s >> 12, midCarry ? 1 : 0]; + } + break; + + case AluFunction.SminusR: + { + f = s + (~r & 0xffff) + cIn; + NibCarry = _carryTableArithmetic[~r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableArithmetic[(~r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableArithmetic[(~r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableArithmetic[(~r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowTable[(~r & 0xffff) >> 12, s >> 12, midCarry ? 1 : 0]; + } + break; + + case AluFunction.RminusS: + { + f = r + (~s & 0xffff) + cIn; + NibCarry = _carryTableArithmetic[r & 0xf, ~s & 0xf, cIn]; + PgCarry = _carryTableArithmetic[(r >> 4) & 0xf, (~s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableArithmetic[(r >> 8) & 0xf, (~s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableArithmetic[(r >> 12) & 0xf, (~s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowTable[r >> 12, (~s & 0xffff) >> 12, midCarry ? 1 : 0]; + } + break; + + case AluFunction.RorS: + { + f = r | s; + + NibCarry = _carryTableOr[r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableOr[(r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableOr[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + Overflow = CarryOut = _carryTableOr[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.RandS: + { + f = r & s; + + NibCarry = _carryTableAnd[r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableAnd[(r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableAnd[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + Overflow = CarryOut = _carryTableAnd[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.notRandS: + { + f = (~r) & s; + + NibCarry = _carryTableAnd[~r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableAnd[(~r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableAnd[(~r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + Overflow = CarryOut = _carryTableAnd[(~r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.RxorS: + { + f = r ^ s; + NibCarry = _carryTableNotXor[~r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableNotXor[(~r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableNotXor[(~r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableNotXor[(~r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowNotXor[(~r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.notRxorS: + { + f = (~r) ^ s; + NibCarry = _carryTableNotXor[r & 0xf, s & 0xf, cIn]; + PgCarry = _carryTableNotXor[(r >> 4) & 0xf, (s >> 4) & 0xf, NibCarry ? 1 : 0]; + bool midCarry = _carryTableNotXor[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled function {0}", i.aF)); + } + + // Clip F to 16 bits + f = f & 0xffff; + + if (loadMAR) + { + // + // If the ALU is being run during a MAR<- operation, the top 8 bits of the ALU are + // computed using an operator specified by aF | 3, with the source set to 0,B. + // The CarryOut and Overflow flags are clear (since they are not affected by the + // OR/notXOR operation), and the carry from the least-significant byte of the ALU does not + // carry over to the most-significant byte. + // + // See page 25 of the microcode ref for details. + // + // We implement this here by simply overwriting the upper byte of F with the upper bits of rB + // (or its complement), and clearing CarryOut and Overflow. + // + switch ((AluFunction)((int)i.aF | 0x3)) + { + case AluFunction.RorS: + { + f = (f & 0xff) | (_r[i.rB] & 0xff00); + bool midCarry = _carryTableOr[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + Overflow = CarryOut = _carryTableOr[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + + case AluFunction.notRxorS: + { + f = (f & 0xff) | ((~_r[i.rB]) & 0xff00); + bool midCarry = _carryTableNotXor[(r >> 8) & 0xf, (s >> 8) & 0xf, PgCarry ? 1 : 0]; + CarryOut = _carryTableNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + Overflow = _overflowNotXor[(r >> 12) & 0xf, (s >> 12) & 0xf, midCarry ? 1 : 0]; + } + break; + } + } + + Zero = (f == 0); + Neg = ((f & 0x8000) != 0); + + // + // Write outputs, do shifts and cycles as appropriate before writing back. + // (Shifts and cycles do not affect the Y output, only the register being written back to.) + // + switch (i.AluDestination) + { + case 0: + _q = (ushort)f; + Y = (ushort)f; + break; + + case 1: + Y = (ushort)f; + break; + + case 2: + Y = _r[i.rA]; + _r[i.rB] = (ushort)f; + break; + + case 3: + _r[i.rB] = (ushort)f; + Y = (ushort)f; + break; + + case 4: + Y = (ushort)f; + + if (i.Cycle) + { + // double-word right shift + // MSB of Q gets inverted LSB of F. + _q = (ushort)((_q >> 1) | ((~f & 0x1) << 15)); + + // MSB of F gets Carry in. + f = (ushort)((f >> 1) | (carryIn ? 0x8000 : 0x0)); + } + else + { + // double-word arithmetic right shift. + // MSB of Q gets inverted LSB of F. + _q = (ushort)((_q >> 1) | ((~f & 0x1) << 15)); + + // MSB of F gets Carry out. + f = (ushort)((f >> 1) | (CarryOut ? 0x8000 : 0x0)); + } + _r[i.rB] = (ushort)f; + break; + + case 5: + Y = (ushort)f; + if (i.Cycle) + { + // F: single-word right rotate: + f = (ushort)((f >> 1) | ((f & 0x1) << 15)); + } + else + { + // F: single-word right shift w/carryIn to MSB: + f = (ushort)((f >> 1) | (carryIn ? 0x8000 : 0x0)); + } + _r[i.rB] = (ushort)f; + break; + + case 6: + Y = (ushort)f; + + // double-word left shift (apparently identical for cycle and shift) + // LSB of F gets MSB of Q, not inverted. + f = (ushort)((f << 1) | ((_q & 0x8000) >> 15)); + + // LSB of Q gets Cin, inverted + _q = (ushort)((_q << 1) | (1 - cIn)); + + _r[i.rB] = (ushort)f; + break; + + case 7: + Y = (ushort)f; + if (i.Cycle) + { + // F: single-word left rotate: + f = (ushort)((f << 1) | ((f & 0x8000) >> 15)); + } + else + { + // F: single-word left shift w/carryIn to LSB: + f = (ushort)((f << 1) | cIn); + } + _r[i.rB] = (ushort)f; + break; + + default: + throw new InvalidOperationException( + String.Format("Unhandled destination {0}", i.aF)); + } + + return Y; + } + + + /// + /// TODO: replace with a nice table lookup. + /// + /// + /// + /// + /// + private static bool CalcOverflow(int r, int s, int cIn) + { + int p0 = (r | s) & 0x1; + int p1 = ((r | s) & 0x2) >> 1; + int p2 = ((r | s) & 0x4) >> 2; + int p3 = ((r | s) & 0x8) >> 3; + + int g0 = (r & s & 0x1); + int g1 = (r & s & 0x2) >> 1; + int g2 = (r & s & 0x4) >> 2; + int g3 = (r & s & 0x8) >> 3; + + int c4 = g3 | (p3 & g2) | (p3 & p2 & g1) | (p3 & p2 & p1 & g0) | (p3 & p2 & p1 & p0 & cIn); + int c3 = g2 | (p2 & g1) | (p2 & p1 & g0) | (p2 & p1 & p0 & cIn); + + return (c3 ^ c4) != 0; + } + + private static bool CalcCarryArithmetic(int r, int s, int cIn) + { + int p0 = (r | s) & 0x1; + int p1 = ((r | s) & 0x2) >> 1; + int p2 = ((r | s) & 0x4) >> 2; + int p3 = ((r | s) & 0x8) >> 3; + + int g0 = (r & s & 0x1); + int g1 = (r & s & 0x2) >> 1; + int g2 = (r & s & 0x4) >> 2; + int g3 = (r & s & 0x8) >> 3; + + int c4 = g3 | (p3 & g2) | (p3 & p2 & g1) | (p3 & p2 & p1 & g0) | (p3 & p2 & p1 & p0 & cIn); + + return c4 != 0; + } + + private static bool CalcCarryOr(int r, int s, int cIn) + { + int p0 = (r | s) & 0x1; + int p1 = ((r | s) & 0x2) >> 1; + int p2 = ((r | s) & 0x4) >> 2; + int p3 = ((r | s) & 0x8) >> 3; + + int c4 = (~(p3 & p2 & p1 & p0) & 0x1) | cIn; + + return c4 != 0; + } + + private static bool CalcCarryAnd(int r, int s, int cIn) + { + int g0 = (r & s & 0x1); + int g1 = (r & s & 0x2) >> 1; + int g2 = (r & s & 0x4) >> 2; + int g3 = (r & s & 0x8) >> 3; + + int c4 = g3 | g2 | g1 | g0 | cIn; + + return c4 != 0; + } + + private static bool CalcCarryNotXor(int r, int s, int cIn) + { + int p0 = (r | s) & 0x1; + int p1 = ((r | s) & 0x2) >> 1; + int p2 = ((r | s) & 0x4) >> 2; + int p3 = ((r | s) & 0x8) >> 3; + + int g0 = (r & s & 0x1); + int g1 = (r & s & 0x2) >> 1; + int g2 = (r & s & 0x4) >> 2; + int g3 = (r & s & 0x8) >> 3; + + int c4 = ~(g3 | (p3 & g2) | (p3 & p2 & g1) | (p3 & p2 & p1 & p0 & (g0 | ~cIn))) & 0x1; + + return c4 != 0; + } + + private static bool CalcOverflowNotXor(int r, int s, int cIn) + { + int p0 = (r | s) & 0x1; + int p1 = ((r | s) & 0x2) >> 1; + int p2 = ((r | s) & 0x4) >> 2; + int p3 = ((r | s) & 0x8) >> 3; + + int g0 = (r & s & 0x1); + int g1 = (r & s & 0x2) >> 1; + int g2 = (r & s & 0x4) >> 2; + int g3 = (r & s & 0x8) >> 3; + + int ovr = ((~p2 | (~g2 & ~p1) | (~g2 & ~g1 & ~p0) | (~g2 & ~g1 & ~g0 & cIn)) ^ + (~p3 | (~g3 & ~p2) | (~g3 & ~g2 & ~p1) | (~g3 & ~g2 & ~g1 & ~p0) | (~g3 & ~g2 & ~g1 & ~g0 & cIn))) & 0x1; + + return ovr != 0; + } + + private static void BuildTables() + { + for (int r = 0; r < 16; r++) + { + for (int s = 0; s < 16; s++) + { + for (int c = 0; c < 2; c++) + { + _overflowTable[r, s, c] = CalcOverflow(r, s, c); + _carryTableArithmetic[r, s, c] = CalcCarryArithmetic(r, s, c); + _carryTableOr[r, s, c] = CalcCarryOr(r, s, c); + _carryTableAnd[r, s, c] = CalcCarryAnd(r, s, c); + _carryTableNotXor[r, s, c] = CalcCarryNotXor(r, s, c); + _overflowNotXor[r, s, c] = CalcOverflowNotXor(r, s, c); + } + } + } + } + + // + // Overflow lookup table for most-significant nibble. + // + private static bool[,,] _overflowTable = new bool[16, 16, 2]; + private static bool[,,] _carryTableArithmetic = new bool[16, 16, 2]; + private static bool[,,] _carryTableOr = new bool[16, 16, 2]; + private static bool[,,] _carryTableAnd = new bool[16, 16, 2]; + private static bool[,,] _carryTableNotXor = new bool[16, 16, 2]; + private static bool[,,] _overflowNotXor = new bool[16, 16, 2]; + + // + // Registers + // + private ushort[] _r = new ushort[16]; + private ushort _q; + + // + // Flags + // + public bool Zero; + public bool Neg; + public bool NibCarry; + public bool PgCarry; + public bool CarryOut; + public bool Overflow; + + // + // Output + // + public ushort Y; + } +} diff --git a/D/CP/CentralProcessor.cs b/D/CP/CentralProcessor.cs new file mode 100644 index 0000000..13e9764 --- /dev/null +++ b/D/CP/CentralProcessor.cs @@ -0,0 +1,1634 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.IOP; +using D.Logging; +using System; + +namespace D.CP +{ + public enum TaskType + { + Emulator = 0, + Display, + Ethernet, + Refresh, + Disk, + IOP, + IOPcs, // Used as an address register when reading/writing control store + Kernel, + } + + public enum ClickType + { + Ethernet0 = 0, + Disk, + IOP, + Ethernet1, + Display + } + + /// + /// IB state, corresponding to the value of iBPtr + /// + public enum IBState + { + Full = 2, + Word = 3, + Byte = 1, + Empty = 0, + } + + /// + /// The Dandelion Central Processor, processor implementation. + /// This partial class implements the logic for the microcode engine. + /// + public partial class CentralProcessor : IIOPDevice, IDMAInterface + { + public CentralProcessor(DSystem system) + { + Reset(); + + _system = system; + } + + public void Reset() + { + _currentTask = TaskType.Kernel; + + _cycle = 1; // Start in C1 + _click = ClickType.Ethernet0; + + // On reset, we need the IOP to wake us up before we + // can start running. + _iopWait_ = true; + + // + // Clear interrupt flags and errors + // + _mInt = false; + _eKErr = 0; + _emulatorErrorTrap = false; + _emulatorErrorTrapClickCount = 0; + + Array.Clear(_microcode, 0, _microcode.Length); + Array.Clear(_microcodeCache, 0, _microcodeCache.Length); + Array.Clear(_tpc, 0, _tpc.Length); + Array.Clear(_tc, 0, _tc.Length); + Array.Clear(_wakeup, 0, _wakeup.Length); + Array.Clear(_rh, 0, _rh.Length); + Array.Clear(_u, 0, _u.Length); + Array.Clear(_link, 0, _link.Length); + Array.Clear(_ib, 0, _ib.Length); + + _tpcAddr = 0; + _stackP = 0; + _ibPtr = IBState.Empty; + _ibFront = 0; + _ibEmptyCancel = false; + _pc16 = false; + _niaModifier = 0; + _niaModifierType = NiaModiferType.Normal; + _marPageCrossBr = false; + _altUAddr = false; + + _swTAddr = false; + _iopAttn = false; + _cpDmaMode = false; + _cpDmaIn = false; + _cpDmaComplete_ = false; + _wakeMode0 = false; + _wakeMode1 = false; + _cpAttn = false; + _emuWake = false; + _cpOutIntReq_ = true; + _cpInIntReq_ = true; + _iopReq = false; + _inLatched = false; + _outLatched = false; + + _exitKernel = false; + } + + public ulong[] MicrocodeRam + { + get { return _microcode; } + } + + public byte[] RH + { + get { return _rh; } + } + + public ushort[] U + { + get { return _u; } + } + + public TaskType CurrentTask + { + get { return _currentTask; } + } + + public int[] TPC + { + get { return _tpc; } + } + + public int NIAModifier + { + get { return _niaModifier; } + } + + public AM2901 ALU + { + get { return _alu; } + } + + public int Cycle + { + get { return _cycle; } + } + + public int StackP + { + get { return _stackP; } + } + + public byte IBFront + { + get { return _ibFront; } + } + + public byte[] IB + { + get { return _ib; } + } + + public IBState IBPtr + { + get { return _ibPtr; } + } + + public bool PC16 + { + get { return _pc16; } + } + + /// + /// Whether the processor is waiting to be awoken by the IOP. + /// + public bool IOPWait + { + get { return _iopWait_; } + } + + /// + /// Used by debuggers to allow stepping macrocode + /// + public bool IBDispatch + { + get + { + bool value = _ibDispatch; + _ibDispatch = false; + return value; + } + } + + /// + /// Executes the specified number of microinstructions + /// + public void ExecuteInstruction(int cycles) + { + for (int c = 0; c < cycles; c++) + { + // + // Let the scheduler run for one clock. + // + _system.Scheduler.Clock(); + + if (_iopWait_) + { + // Still waiting for the IOP to wake us. + continue; + } + + // + // TODO: General: + // Section 2.3.8 of the Dandelion Hardware manual outlines a set of timing restrictions + // for Xbus-related operations. These restrictions are closely related to the mechanisms + // of the CP hardware and are tricky to emulate without actually simulating things at close + // to the component-level. In particular, some Xbus operations are only valid across a subset + // of ALU bits (due to propagation delays between the AM2901s). However: a naive emulation + // should likely work for existing microcode since it would be written to meet these timing + // restrictions -- said emulation would always return a valid result across all bits but this + // satisfies the microcode. Where this falls down is that microcode that would be incorrect + // on a real Dandelion might "work" on the emulator. + // At this time, the naive implementation is Good Enough (tm) and once that's working, making + // a more technically correct implementation can be investigated. + // + + // + // Grab the next instruction from the cache. + // + Microinstruction instruction = _microcodeCache[_tpc[(int)_currentTask]]; + + bool cIn = instruction.Cin; + bool invertPc16 = false; + + // + // If the last instruction caused a PageCross branch during a memory operation, we must + // cancel any MDR<-, IBDisp, or AlwaysIBDisp in this instruction. + // + bool pageCrossCancel = _marPageCrossBr; + _marPageCrossBr = false; + + // + // Latch the NIA modifier bits from the last instruction + // + int niaModifier = _niaModifier; + _niaModifier = 0; + + // + // Latch the AltUAddr bit from the last instruction + // + bool altUAddr = _altUAddr; + _altUAddr = false; + + // + // Save the previous instruction's Y Bus for AltUAddr. + // + ushort lastYBus = _yBus; + + // + // Latch the IBError cancel bit (cancels an MDR<- operation if an IB read + // in c1 failed due to an empty IB). + // + bool ibEmptyCancel = _ibEmptyCancel; + _ibEmptyCancel = false; + + // + // Latch the dispatch type from the last instruction so that we can + // properly modify INIA at the end of this one. + // + NiaModiferType niaModifierType = _niaModifierType; + _niaModifierType = NiaModiferType.Normal; + + // + // Decode XBus sources: + // + + // + // Byte and/or Nibble constants from the microinstruction fields. + // NB: instruction.Byte is set to 0 if this instruction does not specify a Byte or Nibble constant. + // + _xBus = instruction.Byte; + + switch (instruction.fSfZ) + { + case FunctionSelectFZ.fzNorm: + switch ((ZNormFunction)instruction.fZ) + { + case ZNormFunction.LoadIBPtr1: + if (instruction.AlwaysIBDisp || + instruction.LoadIB) + { + // This instruction uses IBPtr<-1 as a modifier; leave ibPtr/ibFront alone here; they will get modified later. + } + else + { + if (_ibPtr != IBState.Byte) + { + _ibPtr = IBState.Byte; + _ibFront = _ib[1]; + } + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "ibPtr<-1: ibPtr={0}, ibFront=0x{1:x2}", _ibPtr, _ibFront); + } + break; + + case ZNormFunction.LoadIBPtr0: + if (_ibPtr != IBState.Word) + { + _ibPtr = IBState.Word; + _ibFront = _ib[0]; + } + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "ibPtr<-0: ibPtr={0}, ibFront=0x{1:x2}", _ibPtr, _ibFront); + break; + + case ZNormFunction.LoadCinFrompc16: + // + // Fun fact (from microcode ref, pg. 21): + // "Due to the way Cin is implemented in the hardware, when the Cin field of the microinstruction is + // 0, the fX version of Cin<-pc16 must be used. (If the fZ version is used, Cin will be 0 instead + // of pc16.) If Cin=1, then either version of Cin<-pc16 can be used. + // + if (cIn) + { + cIn = _pc16; + } + invertPc16 = true; // Invert pc16 at the end of the cycle. + break; + + case ZNormFunction.LoadBank: + throw new NotImplementedException("Bank<- not implemented."); + + case ZNormFunction.AltUaddr: + _altUAddr = true; + break; + + case ZNormFunction.LRot0: + if (instruction.ABypass) + { + _xBus = _alu.R[instruction.rA]; + } + break; + + case ZNormFunction.LRot12: + if (instruction.ABypass) + { + _xBus = (ushort)((_alu.R[instruction.rA] << 12) | (_alu.R[instruction.rA] >> 4)); + } + break; + + case ZNormFunction.LRot8: + if (instruction.ABypass) + { + _xBus = (ushort)((_alu.R[instruction.rA] << 8) | (_alu.R[instruction.rA] >> 8)); + } + break; + + case ZNormFunction.LRot4: + if (instruction.ABypass) + { + _xBus = (ushort)((_alu.R[instruction.rA] << 4) | (_alu.R[instruction.rA] >> 12)); + } + break; + } + break; + + case FunctionSelectFZ.IOXIn: + switch ((ZIOXIn)instruction.fZ) + { + case ZIOXIn.ReadEIdata: + _xBus = _system.EthernetController.EIData(_cycle); + break; + + case ZIOXIn.ReadEStatus: + _xBus = _system.EthernetController.EStatus(); + break; + + case ZIOXIn.ReadKIData: + _xBus = _system.ShugartController.ReadKIData(); + break; + + case ZIOXIn.ReadKStatus: + _xBus = _system.ShugartController.ReadKStatus(); + break; + + case ZIOXIn.KStrobe: + _system.ShugartController.KStrobe(); + break; + + case ZIOXIn.ReadMStatus: + _xBus = _system.MemoryController.MStatus; + break; + + case ZIOXIn.ReadKTest: + _xBus = _system.ShugartController.ReadKTest(); + break; + + case ZIOXIn.EStrobe: + _system.EthernetController.EStrobe(_cycle); + break; + + case ZIOXIn.ReadIOPIData: + _xBus = ReadIOPData(); + if (Log.Enabled) Log.Write(LogComponent.CPControl, "<-IOPData {0:x2}", _xBus); + break; + + case ZIOXIn.ReadIOPStatus: + _xBus = ReadIOPStatus(); + if (Log.Enabled) Log.Write(LogComponent.CPControl, "<-IOPStatus {0} ({1:x2})", (IOPStatusFlags)_xBus, _xBus); + break; + + case ZIOXIn.ReadErrnIBnStkp: + // uCode reference, p.33: + // "X[8-9] = EKerr, X[10-11] = ~ibPtr, X[12-15] = ~stackP + // + _xBus = + (ushort)((_eKErr << 6) | + ((~(int)_ibPtr & 0x3) << 4) | + (~_stackP & 0xf)); + break; + + case ZIOXIn.ReadRH: + _xBus = _rh[instruction.rB]; + break; + + case ZIOXIn.ReadibNA: + if (_ibPtr == IBState.Empty) + { + // IB is empty, trap. + SignalErrorTrap(ErrorTrap.IBEmpty); + + // Cancel MDR<- in c2 + _ibEmptyCancel = _cycle == 1; + } + else + { + // xBus <- ibFront, leave ibPtr alone. + _xBus = _ibFront; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "<-ibNA 0x{0:x2} ibPtr={1}", _ibFront, _ibPtr); + } + break; + + case ZIOXIn.Readib: + if (_ibPtr == IBState.Empty) + { + // IB is empty, trap. + SignalErrorTrap(ErrorTrap.IBEmpty); + + // Cancel MDR<- in c2 + _ibEmptyCancel = _cycle == 1; + } + else + { + // xBus <- ibFront, decrement ibPtr. + _xBus = _ibFront; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "<-ib 0x{0:x2} ibPtr={1}", _ibFront, _ibPtr); + + _ibFront = _ib[((int)_ibPtr) & 0x1]; + DecrementIBPtr(); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "ibFront now 0x{0:x2} ibPtr={1}", _ibFront, _ibPtr); + } + break; + + case ZIOXIn.ReadibLow: + if (_ibPtr == IBState.Empty) + { + // IB is empty, trap. + SignalErrorTrap(ErrorTrap.IBEmpty); + + // Cancel MDR<- in c2 + _ibEmptyCancel = _cycle == 1; + } + else + { + // low nibble of ibFront, leave ibPtr alone. + _xBus = (ushort)(_ibFront & 0xf); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "<-ibLow 0x{0:x2} ibPtr={1}", _xBus, _ibPtr); + } + break; + + case ZIOXIn.ReadibHigh: + if (_ibPtr == IBState.Empty) + { + // IB is empty, trap. + SignalErrorTrap(ErrorTrap.IBEmpty); + + // Cancel MDR<- in c2 + _ibEmptyCancel = _cycle == 1; + } + else + { + // high nibble of ibFront, leave ibPtr alone. + _xBus = (ushort)(_ibFront >> 4); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "<-ibHigh 0x{0:x2} ibPtr={1}", _xBus, _ibPtr); + } + break; + } + break; + } + + if (instruction.fX == XFunction.LoadCinFrompc16) + { + cIn = _pc16; + invertPc16 = true; // Invert pc16 at the end of the cycle. + } + + if (instruction.SURead) + { + // SU read operations + switch ((int)instruction.fSfZ) + { + case 0: + case 1: + _xBus = _u[_stackP]; + break; + + case 2: + case 3: + if (altUAddr) + { + // U address is rA,,Y[12-15] + // (Y bus from *previous* instruction.) + _xBus = _u[(instruction.rA << 4) | (lastYBus & 0xf)]; + } + else + { + // U address is rA,,fZ + _xBus = _u[instruction.UAddress]; + } + break; + } + } + + // Handle <-MD instructions which occur only in C3. + if (instruction.mem && _cycle == 3) + { + bool valid = false; + _xBus = _system.MemoryController.ReadMD(_currentTask, out valid); + + if (!valid) + { + // + // Read from non-existent or uncorrectable memory. This causes a double-bit memory fault. + // + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPError, "Invalid memory access, address 0x{0:x5}", _system.MemoryController.MAR); + SignalErrorTrap(ErrorTrap.EmulatorMemoryError); + } + } + + // + // Generate new Y bus value after feeding it through the ALU. + // + _yBus = _alu.Execute(instruction, _xBus, cIn, (instruction.mem && _cycle == 1)); + + // + // Handle MAR<-, Map<- and MDR<-, which take their input from the Y Bus (+ YH for MAR<-). + // + if (instruction.MarMapMDR) + { + switch (_cycle) + { + case 1: + + if (instruction.LoadMap) + { + // + // Map<- : This is 0x10000 + YH[0-7],,Y[0-7], providing a 24-bit virtual address indexing + // into the virtual memory map (64k). + // Earlier CP boards only supported 22-bit VA's (using only YH[2-7], indexing into a 16k map.) + // +#if TWENTYTWOBITVA + int mapAddr = 0x10000 + ((((_rh[instruction.rB] & 0x3f) << 16) | _yBus) >> 8); + _system.MemoryController.LoadMAR(mapAddr); + + if ((_rh[instruction.rB] & 0xc0) != 0) + { + SignalErrorTrap(ErrorTrap.EmulatorMemoryError); + } +#else + int mapAddr = 0x10000 + (((_rh[instruction.rB] << 16) | _yBus) >> 8); + _system.MemoryController.LoadMAR(mapAddr); +#endif + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPMap, "Map<- 0x{0:x8} address loaded.", mapAddr); + } + else + { + // + // MAR<-: This is YH[4..7],,Y, providing a 20-bit physical address. + // Earlier CP boards only supported 18-bit physical addresses. + // +#if EIGHTEENBITPA + _system.MemoryController.LoadMAR(((_rh[instruction.rB] & 0x3) << 16) | _yBus); +#else + _system.MemoryController.LoadMAR(((_rh[instruction.rB] & 0xf) << 16) | _yBus); +#endif + } + + // + // Do MAR<- side-effects: pageCross branch + // See the HW ref, page 25. + // pageCross is the XOR of the carry out from the low byte of the ALU with af[2]. + // + if (instruction.mem && (_alu.PgCarry ^ (((int)instruction.aF & 0x1) == 1))) + { + // MAR<- enables a pageCross branch in INIA[10]. + _niaModifier |= 0x2; + _marPageCrossBr = true; + } + break; + + case 2: + // + // Sanity check that Map<- is not occurring in c2: + // + if (instruction.LoadMap) + { + throw new InvalidOperationException("Map<- in c2"); + } + + // MDR<- + if (!pageCrossCancel && !ibEmptyCancel) + { + _system.MemoryController.LoadMDR(_yBus); + } + break; + + case 3: + // + // Sanity check that Map<- is not occurring in c3: + // + if (instruction.LoadMap) + { + throw new InvalidOperationException("Map<- in c3"); + } + break; + } + } + + // + // Late LRotn functions. + // These take the ALU output on the Y Bus and apply the specified rotation, + // putting the result on the X Bus. + // + if (instruction.LateLRotN) + { + switch ((ZNormFunction)instruction.fZ) + { + case ZNormFunction.LRot0: + _xBus = _yBus; + break; + + case ZNormFunction.LRot12: + _xBus = (ushort)((_yBus << 12) | (_yBus >> 4)); + break; + + case ZNormFunction.LRot8: + _xBus = (ushort)((_yBus << 8) | (_yBus >> 8)); + break; + + case ZNormFunction.LRot4: + _xBus = (ushort)((_yBus << 4) | (_yBus >> 12)); + break; + } + } + + // + // Load RH register from X Bus. + // + if (instruction.fX == XFunction.LoadRH) + { + _rh[instruction.rB] = (byte)_xBus; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPExecution, "RH[0x{0:x2}] = 0x{1:x2}", instruction.rB, _xBus); + } + + // Handle fY functions that aren't implicitly handled elsewhere (cycle, Byte, etc.) + switch (instruction.fSfY) + { + case FunctionSelectFY.fyNorm: + switch ((YNormFunction)instruction.fY) + { + case YNormFunction.ExitKern: + // + // Microcode ref, p 34 (section K.3): + // "When executed in c1, ExitKern will cause normal task scheduling to begin. Thus, + // which task runs in the click following ExitKern depends on where in the round structure + // the ExitKern occured. [sic]" + // + if (_cycle == 1) + { + _exitKernel = true; + } + break; + + case YNormFunction.EnterKern: + throw new NotImplementedException("EnterKern not implemented."); + + case YNormFunction.ClrIntErr: + // + // Clear pending interrupts and error state: + // + // HwRef sec 2.5.3: + // "MInt is ... cleared with fY = ClrIntErr. (ClrIntErr also resets + // the EKErr register.)" + // + // HwRef sec 2.5.5.2: + // "[EKErr is] Cleared by ClrIntErr, which, as a side-effect, also resets any pending interrupts." + // + _mInt = false; + _eKErr = 0; + break; + + case YNormFunction.IBDisp: + // + // NB: AlwaysIBDisp is not a special function, it is an assembler macro for IBDisp, IBPtr<- 1. + // This is canceled if the last memory operation resulted in a page cross. + if (!pageCrossCancel) + { + if ((_ibPtr != IBState.Full || _mInt) && !instruction.AlwaysIBDisp) + { + // + // From hw ref (2.5.5.1) + // If an IBDisp is executed and ibPtr != full, the dispatch does not occur and instead a microcode trap is invoked. + // The jump to the trap location occurs at the end of the next cycle (unlike emulator error traps which happen + // in a future c1). + // - INIA[0-3] is replaced with 4 when ibPtr = empty or 5 when ibPtr != empty + // - If there is a pending Mesa interrupt request (_mInt = 1): + // - INIA[0-3] is replaced with 6 if ibPtr = empty or full, or 7 otherwise. + // Regardless, ibPtr does not change. + // + // A non-trapping IBDispatch is forced by fZ = IBPtr<-1 (i.e. AlwaysIBDisp). + // + if (_mInt) + { + _niaModifier |= (_ibPtr == IBState.Empty || _ibPtr == IBState.Full) ? 0x600 : 0x700; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "Mint trap to 0x{0:x3}", _niaModifier); + + } + else + { + _niaModifier |= (_ibPtr == IBState.Empty) ? 0x400 : 0x500; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "IB refill trap to 0x{0:x3}", _niaModifier); + } + + _niaModifierType = NiaModiferType.IBRefillTrap; + } + else + { + // + // Do the normal dispatch, and decrement ibPtr when done. + // Unlike other dispatches, ibFront replaces some bits (rather than just or'ing) of + // the next instruction's INIA. This needs to be specially handled. + // "The high 4 bits of ibFront replace INIA[4-7] while the low 4 bits of ibFront are OR'd + // with INIA[8-11] (thereby allowing simultaneous branch/dispatches). INIA[0-3] is unaffected. + // + _niaModifier |= _ibFront; + _niaModifierType = NiaModiferType.IBDispatch; + + /* + MacroInstruction inst = MacroInstruction.GetInstruction(MacroType.Lisp, _ibFront); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPInst, + "PC 0x{0:x5} - 0x{1:x2} ({2}) ibPtr ({3}) {4}", + ((((_rh[5] & 0xf) << 16) | _alu.R[5]) << 1) | (_pc16 ? 1 : 0), + _ibFront, + inst.Mnemonic, + inst.Operand, + _ibPtr); + */ + + // new ibFront is IB[ibPtr[1]] + _ibFront = _ib[((int)_ibPtr) & 0x1]; + + DecrementIBPtr(); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "<-ibFront 0x{0:x2} ibPtr={1}", _ibFront, _ibPtr); + } + } + break; + + case YNormFunction.MesaIntRq: + _mInt = true; + break; + + case YNormFunction.LoadIB: + if (instruction.LoadIBPtr1) + { + // + // If buffer is empty, the low byte goes to ibFront and the high + // byte is discarded, otherwise load ib[0] and ib[1] and leave + // ibFront alone. (hwref, p. 20) + // + if (_ibPtr != IBState.Empty) + { + _ib[0] = (byte)(_xBus >> 8); + _ib[1] = (byte)_xBus; + + // new ibPtr: "if empty THEN byte ELSE full" + _ibPtr = IBState.Full; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "ibPtr<-1 IB<-, notempty: ibPtr={0}, ib[0]=0x{1:x2} ib[1]=0x{2:x2}", _ibPtr, _ib[0], _ib[1]); + } + else + { + // new ibPtr: "if empty THEN byte ELSE full" + _ibPtr = IBState.Byte; + + // new ibFront: "if ibPtr = empty THEN X[8-15] ELSE unchanged" + _ibFront = (byte)_xBus; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "ibPtr<-1 IB<-, empty: ibPtr={0}, ibFront=0x{1:x2}", _ibPtr, _ibFront); + } + } + else + { + // low byte to ib[1] + _ib[1] = (byte)_xBus; + + if (_ibPtr != IBState.Empty) + { + // high byte to ib[0] + _ib[0] = (byte)(_xBus >> 8); + + // new ibPtr: "if empty THEN word ELSE full" + _ibPtr = IBState.Full; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "IB<-, notempty: ibPtr={0}, ib[0]=0x{1:x2} ib[1]=0x{2:x2}", _ibPtr, _ib[0], _ib[1]); + } + else + { + // new ibFront: "if ibPtr = empty THEN X[0-7] ELSE unchanged" + _ibFront = (byte)(_xBus >> 8); + + // new ibPtr: "if empty THEN word ELSE full" + _ibPtr = IBState.Word; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPIB, "IB<-, empty: ibPtr={0}, ibFront=0x{1:x2}, ib[0]=0x{2:x2} ib[1]=0x{3:x2}", _ibPtr, _ibFront, _ib[0], _ib[1]); + } + } + break; + + case YNormFunction.ClrDPRq: + _system.DisplayController.ClrDpRq(); + break; + + case YNormFunction.ClrIOPRq: + SleepTask(TaskType.IOP); + break; + + case YNormFunction.ClrRefRq: + SleepTask(TaskType.Refresh); + break; + + case YNormFunction.ClrKFlags: + _system.ShugartController.ClrKFlags(); + break; + } + break; + + case FunctionSelectFY.DispBr: + switch ((YDispBrFunction)instruction.fY) + { + case YDispBrFunction.NegBr: + if (_alu.Neg) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.ZeroBr: + if (_alu.Zero) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.NZeroBr: + if (!_alu.Zero) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.MesaIntBr: + if (_mInt) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.PgCarryBr: + if (_alu.PgCarry) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.CarryBr: + if (_alu.CarryOut) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.XRefBr: + // + // Dispatch on X[11] into INIA[11] + // + _niaModifier |= (_xBus & 0x10) >> 4; + break; + + case YDispBrFunction.NibCarryBr: + if (_alu.NibCarry) + { + _niaModifier |= 1; + } + break; + + case YDispBrFunction.XDisp: + _niaModifier |= (_xBus & 0xf); + break; + + case YDispBrFunction.YDisp: + _niaModifier |= (_yBus & 0xf); + break; + + case YDispBrFunction.XC2npcDisp: + // + // Dispatch on X[12-13],,cycle 2,,~pc16 into INIA[8-11] + // + _niaModifier |= (_xBus & 0xc) | (_cycle == 2 ? 0x2 : 0x0) | (_pc16 ? 0x0 : 0x1); + break; + + case YDispBrFunction.YIODisp: + // + // Dispatch on Y[12-13],,bp[39],,bp[139] + // Also known as EtherDisp. + // + _niaModifier |= (_yBus & 0xc) | (_system.EthernetController.EtherDisp()); + break; + + case YDispBrFunction.XwdDisp: + // + // Dispatch on X.9,,X.10 into INIA 10,,11 + // + _niaModifier |= (_xBus & 0x60) >> 5; + break; + + case YDispBrFunction.XHDisp: + // + // Dispatch on X.4,,X.0 + // + _niaModifier |= ((_xBus & 0x8000) >> 15) | ((_xBus & 0x0800) >> 10); + break; + + case YDispBrFunction.XLDisp: + // + // Dispatch on X.8,,X.15 + // + _niaModifier |= (_xBus & 0x1) | ((_xBus & 0x80) >> 6); + break; + + case YDispBrFunction.PgCrOvDisp: + // + // Dispatch on _pageCross,,ALU overflow bit into INIA 10,,11. + // + // See the HW ref, page 25. + // pageCross is the XOR of the carry out from the low byte of the ALU with af[2]. + // + _niaModifier |= (_alu.PgCarry ^ (((int)instruction.aF & 0x1) == 1) ? 0x2 : 0x0) | (_alu.Overflow ? 0x1 : 0x0); + break; + } + break; + + case FunctionSelectFY.IOOut: + switch ((YIOOutFunction)instruction.fY) + { + case YIOOutFunction.IOPOData: + if (Log.Enabled) Log.Write(LogComponent.CPControl, "IOPOData<- {0:x2}", (byte)_xBus); + WriteIOPData((byte)_xBus); + break; + + case YIOOutFunction.IOPCtl: + WriteIOPCtl((byte)_xBus); + break; + + case YIOOutFunction.KOData: + _system.ShugartController.SetKOData(_xBus); + break; + + case YIOOutFunction.KCtl: + _system.ShugartController.SetKCtl(_xBus); + break; + + case YIOOutFunction.EOData: + _system.EthernetController.EOData(_xBus); + break; + + case YIOOutFunction.EICtl: + _system.EthernetController.EICtl(_xBus); + break; + + case YIOOutFunction.DCtlFifo: + _system.DisplayController.SetDCtlFifo(_yBus); + break; + + case YIOOutFunction.DCtl: + _system.DisplayController.SetDCtl(_xBus); + break; + + case YIOOutFunction.DBorder: + _system.DisplayController.SetDBorder(_yBus); + break; + + case YIOOutFunction.PCtl: + // Assume this is for the LSEP controller; docs are thin. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.CPExecution, "PCtl<-0x{0}, unimplemented.", _xBus); + + if ((_xBus & 0x1) != 0) + { + // Per MoonCycle.mc, + // This wakes the LSEP/Refresh task (task 3). + WakeTask(TaskType.Refresh); + } + else + { + SleepTask(TaskType.Refresh); + } + break; + + case YIOOutFunction.MCtl: + _system.MemoryController.SetMCtl(_yBus); + break; + + case YIOOutFunction.EOCtl: + _system.EthernetController.EOCtl(_xBus); + break; + + case YIOOutFunction.KCmd: + _system.ShugartController.SetKCmd(_xBus); + break; + + case YIOOutFunction.POData: + throw new NotImplementedException("POData not implemented."); + break; + + case YIOOutFunction.Invalid0: + case YIOOutFunction.Invalid1: + // Just a no-op + break; + } + break; + } + + // SU reg write + if (instruction.SUWrite) + { + switch ((int)instruction.fSfZ) + { + case 0: + case 1: + _u[_stackP] = _yBus; + break; + + case 2: + case 3: + if (altUAddr) + { + // U address is rA,,Y[12-15] + // (Y bus from *previous* instruction.) + _u[(instruction.rA << 4) | (lastYBus & 0xf)] = _yBus; + } + else + { + // U address is rA,,fZ + _u[instruction.UAddress] = _yBus; + } + break; + } + } + + // + // pc16 gets inverted at the end of the cycle if fX or fZ is Cin<-pc16. + // (HW ref, section 2.3.7) + // + if (invertPc16) + { + _pc16 = !_pc16; + } + + // + // Stack modifications occur at the end of the microinstruction, handle them here. + // + if (instruction.LoadStackP) + { + _stackP = (_yBus & 0xf); + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stackp loaded, now 0x{0:x}", _stackP); + } + + // + // Handle pushes, pops, and stack pointer tests performed by performing multiple + // pops/pushes at the same time. + // + if (instruction.StackOperation) + { + switch (instruction.StackTest) + { + case StackTestType.None: + // Normal stack behavior. + if (instruction.Push) + { + if (_stackP == 0xf) + { + // Overflow + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack overflow, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + + _stackP = (_stackP + 1) & 0xf; + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Push: Stack pointer is now 0x{0:x}", _stackP); + } + else if (instruction.DoublePop) + { + // Test for 2 word underflow. + if (_stackP < 2) + { + // Underflow + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack underflow, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + + // Still only decrement stackP by 1. + _stackP = (_stackP - 1) & 0xf; + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Double Pop: Stack pointer is now 0x{0:x}", _stackP); + } + else if (instruction.Pop) + { + if (_stackP == 0) + { + // Underflow + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack underflow, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + + _stackP = (_stackP - 1) & 0xf; + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Pop: Stack pointer is now 0x{0:x}", _stackP); + } + break; + + case StackTestType.Underflow: + // Test if a pop would cause an underflow + if (_stackP == 0x0) + { + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack underflow test passed, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + break; + + case StackTestType.Overflow: + // Test if a push would cause an overflow + if (_stackP == 0xf) + { + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack overflow test passed, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + break; + + case StackTestType.Underflow2: + // Test if stackP is 0 or 1 + if (_stackP < 2) + { + if (Log.Enabled) Log.Write(LogComponent.CPStack, "Stack underflow (2) test passed, raising error trap."); + SignalErrorTrap(ErrorTrap.StackOverUnderflow); + } + break; + } + } + + // + // Calculate NIA based on this instruction's INIA field and condition/dispatch bits from the last + // instruction. + // + int nia = instruction.INIA; + + // + // If an error trap is pending we cancel any pending IBRefill trap. + // + if (_eKErr > 0 && niaModifierType == NiaModiferType.IBRefillTrap) + { + niaModifierType = NiaModiferType.Normal; + niaModifier = 0; + if (Log.Enabled) Log.Write(LogComponent.CPError, "Error trap pending, IBRefill trap aborted."); + } + + switch (niaModifierType) + { + case NiaModiferType.Normal: + // Normal dispatch, just OR the bits in + nia |= niaModifier; + break; + + case NiaModiferType.IBDispatch: + // IBDisp dispatch: bits [4-7] are replaced, bits [8-11] are or'd. + nia = (nia & 0xf0f) | niaModifier; + + // + // Set the IBDispatch breakpoint flag so that debuggers + // can be notified of a new Mesa instruction. + // + _ibDispatch = true; + break; + + case NiaModiferType.IBRefillTrap: + // Refill Trap: bits [0-3] are replaced. + nia = (nia & 0x0ff) | niaModifier; + break; + } + + // + // Dispatch to the calculated NIA. + // + _tpc[(int)_currentTask] = nia; + + // + // OR link bits into the next instruction's NIA (or save them) as necessary. + // + if (instruction.LinkAddress != -1) + { + // Modify the link register on a write (indicated by NIA[7] == 0) + if ((nia & 0x10) == 0) + { + // + // Save the low nibble only. + // + _link[instruction.LinkAddress] = nia & 0xf; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPExecution, "Link[{0}] = {1:x}", instruction.LinkAddress, _link[instruction.LinkAddress]); + } + // Modify the NIA based on the link register (NIA[7] == 1) + else + { + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPExecution, "nia modifier ({0:x3}), Link[{1}] = {2:x}", _niaModifier, instruction.LinkAddress, _link[instruction.LinkAddress]); + // + // Or the link register in. + // + _niaModifier |= _link[instruction.LinkAddress]; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.CPExecution, "nia modifier now {0:x3}", nia); + } + } + + _cycle++; + + if (_cycle > 3) + { + _cycle = 1; + TaskSwitch(); + } + } + } + + public void WakeTask(TaskType task) + { + _wakeup[(int)task] = true; + + if (Log.Enabled) Log.Write(LogComponent.CPTask, "Task {0} set to wake.", task); + } + + /// + /// Yes I know that "sleep" is an intransitive verb. + /// + /// + public void SleepTask(TaskType task) + { + _wakeup[(int)task] = false; + + if (Log.Enabled) Log.Write(LogComponent.CPTask, "Task {0} set to sleep.", task); + } + + /// + /// Invoked at the end of a click. If a task switch is necessary the proper task is selected and switched to. + /// + private void TaskSwitch() + { + // + // Move to the next click. This happens even while executing the Kernel task, though it + // causes no task switching in that case. + // + _click = (ClickType)(((int)_click + 1) % 5); + + // if (Log.Enabled) Log.Write(LogComponent.CPTask, "Switch to click {0}", _click); + + // + // If we are executing in the Kernel task and we aren't being asked to leave via ExitKernel + // then no task switching takes place. + // + if (_exitKernel || _currentTask != TaskType.Kernel) + { + if (_exitKernel) + { + // Remove the Kernel task wakeup signal. + SleepTask(TaskType.Kernel); + _exitKernel = false; + } + + switch (_click) + { + case ClickType.Ethernet0: + case ClickType.Ethernet1: + DoTaskSwitch(TaskType.Ethernet); + break; + + case ClickType.Disk: + DoTaskSwitch(TaskType.Disk); + break; + + case ClickType.IOP: + DoTaskSwitch(TaskType.IOP); + break; + + case ClickType.Display: + if (_system.DisplayController.DisplayOn) + { + DoTaskSwitch(TaskType.Display); + } + else + { + DoTaskSwitch(TaskType.Refresh); + } + break; + } + + _exitKernel = false; + } + + // + // If an emulator error trap occurred during some prior click and we're in the emulator + // task now, trap to location 0 when the click count reaches zero. + // + if (_emulatorErrorTrap && _currentTask == TaskType.Emulator) + { + _emulatorErrorTrapClickCount--; + + if (_emulatorErrorTrapClickCount == 0) + { + _emulatorErrorTrap = false; + _tpc[(int)_currentTask] = 0; + if (Log.Enabled) Log.Write(LogComponent.CPError, "Taking trap to 0 for eKerr {0}", _eKErr); + } + } + } + + private void DoTaskSwitch(TaskType newTask) + { + TaskType nextTask; + // + // If the Kernel has been awoken we switch to the Kernel task regardless of anything else. + // + if (WakeStatus(TaskType.Kernel)) + { + nextTask = TaskType.Kernel; + if (Log.Enabled) Log.Write(LogComponent.CPTask, "Waking Kernel task.", _click); + } + else + { + // + // If there is a wakeup for the requested task then we switch to that task, + // otherwise we default to the Emulator task. + // + nextTask = WakeStatus(newTask) ? newTask : TaskType.Emulator; + } + + if (nextTask == _currentTask) + { + // Nothing changed, nothing to do, early return. + // if (Log.Enabled) Log.Write(LogComponent.CPTask, "No task switch this click."); + return; + } + + // + // Save the current task's NIA condition bits to the TC array. Only the 4 low bits are saved. + // + _tc[(int)_currentTask] = _niaModifier & 0xf; + + // + // Restore the new tasks's NIA condition bits + // + _niaModifier = _tc[(int)nextTask]; + + // + // Switch to the new task. + // + _currentTask = nextTask; + + if (Log.Enabled) Log.Write(LogComponent.CPTask, "Task switch to {0}", _currentTask); + } + + private bool WakeStatus(TaskType task) + { + return _wakeup[(int)task]; + } + + private void SignalErrorTrap(ErrorTrap err) + { + if (err == ErrorTrap.ControlStoreParity) + { + // Not implemented, probably will never be... + throw new NotImplementedException("CS Parity errors not implemented."); + } + + // Set the emulator-kernel error register. + // TODO: Smaller values have priority over larger. + if ((int)err < _eKErr || (_eKErr == 0 && !_emulatorErrorTrap)) + { + _eKErr = (int)err; + } + + // + // An error trap transfers control of the Emulator task + // to location 0; this will occur in c1 one or two emulator clicks in the future (including + // the current click) depending on the trap and the cycle the trap occurred in. + // The hardware requires the execution of one additional Emulator click before the trap. + // (hwref, pg. 33). + // + if (!_emulatorErrorTrap) + { + _emulatorErrorTrap = true; + switch (err) + { + case ErrorTrap.ControlStoreParity: + // + // "If the instruction read from microstore in c1 has bad parity, then the Kernel + // runs at location 0 in the next c1. If the parity error occurs in c2 or c3, then there + // is a one click delay before the Kernel executes at location 0 in c1. + // + _emulatorErrorTrapClickCount = _cycle == 1 ? 1 : 2; + break; + + case ErrorTrap.EmulatorMemoryError: + // "The hardware requires the execution of one additional Emulator click between the c3 which + // errored and the trap at location 0." + _emulatorErrorTrapClickCount = 2; + break; + + case ErrorTrap.StackOverUnderflow: + // "The hardware requires the execution of on additional Emulator click before the trap at location 0." + _emulatorErrorTrapClickCount = 2; + break; + + case ErrorTrap.IBEmpty: + // "If the IB-Empty error occurs in c1, then control transfers to location 0 in the next Emulator c1. + // However if the error occurs in c2 or c3, the hardware requires the execution of one additional + // Emulator click before the trap at location 0." + _emulatorErrorTrapClickCount = _cycle == 1 ? 1 : 2; + break; + } + if (Log.Enabled) Log.Write(LogComponent.CPError, "Error trap {0} at address {1:x3}. Jumping to CS address 0 for task {2} in future c1.", err, _tpc[(int)_currentTask], _currentTask); + } + } + + /// + /// Does the unique "decrement" of ibPtr -- 2, 3, 1, 0 (full, word, byte, empty) + /// + private void DecrementIBPtr() + { + _ibPtr = _nextIBPtr[(int)_ibPtr]; + } + + private enum ErrorTrap + { + ControlStoreParity = 0, + EmulatorMemoryError = 1, + StackOverUnderflow = 2, + IBEmpty = 3, + } + + private enum NiaModiferType + { + Normal, + IBDispatch, + IBRefillTrap, + } + + // Task/Temporary Program Counters + private int[] _tpc = new int[8]; + + // Task/Temporary Condition bits (NIA modifiers). This is only 4 bits. + private int[] _tc = new int[8]; + + // Task wakeups + private bool[] _wakeup = new bool[8]; + + // Current task + private TaskType _currentTask; + + // Microcode store + private ulong[] _microcode = new ulong[4096]; + + // Microcode decode cache + private Microinstruction[] _microcodeCache = new Microinstruction[4096]; + + // 2901 ALU + private AM2901 _alu = new AM2901(); + + // RH registers, 8 bit + private byte[] _rh = new byte[16]; + + // Link registers, 4 bit + // NOTE: Link register: + // See section 2.5.4 of the HW ref; + // Link is addressed by fX and is written with the low nibble of NIAX when + // fX is in 0..7 and NIA[7] = 0; + // A Link register is or'd into the low nibble of INIA when fX is in 0..7 and + // NIA[7] = 1. If the preceding uinstruction does not specify a branch/dispatch, + // the Link register is loaded with a constant. + // However if the prior instruction does specify branch/dispatch, the value loaded + // depends on the outcome of the branch or dispatch. + private int[] _link = new int[8]; + + // U registers + private ushort[] _u = new ushort[256]; + + // Instruction buffer (IB) + private byte _ibFront; + private byte[] _ib = new byte[2]; + private IBState _ibPtr; + private bool _ibEmptyCancel; + + // Table of values for next ipPtr value when decrementing ibPtr. + private readonly IBState[] _nextIBPtr = { IBState.Empty, IBState.Empty, IBState.Word, IBState.Byte }; + + // Stack pointer, 4 bits + private int _stackP; + + // pc16 register, 1 bit + private bool _pc16; + + // Bus data + private ushort _xBus; + private ushort _yBus; + + // NIA modifier for branch/dispatch + private int _niaModifier; + private NiaModiferType _niaModifierType; + + // AltUAddress flag + private bool _altUAddr; + + // + // Interrupt flags + // + private bool _mInt; + + // + // Error state + // + + // + // HWRef, section 2.5.5.2: + // The EKErr register, read onto X[8-9] with <-ErrnIBnStkp, names the type of error: + // 0 - control store parity error + // 1 - Emulator memory error + // 2 - stackPointer overflow or underflow + // 3 - IB-Empty error + // If, coincidentally, two or more error occur at the same time, smaller values of EKErr + // are reported. The error types are also accumulated until EKErr is reset: the minimum + // value is reported when EKErr is read. + // Cleared by ClrIntErr, which, as a side-effect, also resets any pending interrupts. + private int _eKErr; + private bool _emulatorErrorTrap; + private int _emulatorErrorTrapClickCount; + + /// + /// Whether a PageCross branch occurred during the last MAR<- operation. + /// Cleared at the beginning of the next instruction, and used to indicate whether + /// an MDR<-, IBDisp, or AlwaysIBDisp should be canceled. + /// + private bool _marPageCrossBr; + + // + // Cycle / Click / Round data + // + private int _cycle; // c1 ... c3 + private ClickType _click; // 0 ... 4 + + // + // Whether to exit the Kernel task at the end of this click + // + private bool _exitKernel; + + // + // Debugging flag: Indicates that an IBDispatch has occurred, + // allows handling Mesa (or other bytecode) instruction breakpoints. + // + private bool _ibDispatch; + + // + // The D System we belong to + // + private DSystem _system; + } +} diff --git a/D/CP/CentralProcessorIO.cs b/D/CP/CentralProcessorIO.cs new file mode 100644 index 0000000..8e7aaa2 --- /dev/null +++ b/D/CP/CentralProcessorIO.cs @@ -0,0 +1,712 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.IOP; +using D.Logging; +using System; + +namespace D.CP +{ + // + // From SysDefs.asm: + // Bits 0:5 - (IOPWait', SwTAddr', IOPattn, CPDmaMode, CPDmaIn) + // (in the usual Xerox reverse order) + // + [Flags] + public enum CPControlFlags + + { + IOPWait_ = 0x80, + SwTAddr_ = 0x40, + IOPattn = 0x20, + CPDmaMode = 0x10, + CPDmaIn = 0x08, + } + + // + // From IOP schematics, p 15-17: + // + public enum CPStatusFlags + { + CPAttn = 0x80, + EmuWake = 0x40, + IOPAttn_ = 0x20, + CPDmaMode_ = 0x10, + CPDmaIn_ = 0x08, + CPInIntReq_ = 0x04, + CPOutIntReq_ = 0x2, + CPDmaComplete_ = 0x1, + } + + [Flags] + public enum IOPCtlFlags + { + EmuWake = 0x8, + CPAttn = 0x4, + WakeMode0 = 0x2, + WakeMode1 = 0x1, + } + + [Flags] + public enum IOPStatusFlags + { + IOPAttn = 0x20, + EmuWake_ = 0x10, + CPAttn_ = 0x8, + WakeMode0_ = 0x4, + WakeMode1_ = 0x2, + IOPReq = 0x1, + } + + /// + /// The Dandelion Central Processor, I/O implmentation. + /// This partial class implements the CP<->IOP communication channel, + /// and IOP microcode loading logic. + /// + public partial class CentralProcessor : IIOPDevice, IDMAInterface + { + // + // IOP port info + // + public int[] ReadPorts + { + get { return _readPorts; } + } + + public int[] WritePorts + { + get { return _writePorts; } + } + + public void WritePort(int port, byte value) + { + switch ((PortWriteRegister)port) + { + case PortWriteRegister.CPDataOut: + WriteCPInBuffer(value); + break; + + case PortWriteRegister.CPControl: + WriteCPCtl(value); + break; + + case PortWriteRegister.CPClrDmaComplete: + _cpDmaComplete_ = false; + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "CP DMA complete flag cleared."); + break; + + case PortWriteRegister.CPCSa: + case PortWriteRegister.CPCSb: + case PortWriteRegister.CPCSc: + case PortWriteRegister.CPCSd: + case PortWriteRegister.CPCSe: + case PortWriteRegister.CPCSf: // CS microcode word, MSB ($F8) to LSB ($FD) + WriteIOPMicrocodeWord(port - 0xf8, (byte)~value); + + if (port == 0xfd) + { + if (Log.Enabled) Log.Write(LogComponent.CPMicrocodeLoad, "CS word {0:x3} completed: {1:x12} {2}", _tpc[6], _microcode[_tpc[6]], new Microinstruction(_microcode[_tpc[6]]).Disassemble(-1)); + } + break; + + case PortWriteRegister.TPCHigh: // TPC high : TPCAddr[0:2],,TPCData[0:4]' + _tpcAddr = value >> 5; + _tpcTemp = ((~value & 0x1f) << 7); + if (Log.Enabled) Log.Write(LogComponent.CPTPCLoad, "TPC high written: TPC[{0}] ({1}) is now {2:x3}", _tpcAddr, (TaskType)_tpcAddr, _tpcTemp); + break; + + case PortWriteRegister.TPCLow: // TPC low : don't care,,TPCData[5:11]' + _tpc[_tpcAddr] = _tpcTemp | (~value & 0x7f); + if (Log.Enabled) Log.Write(LogComponent.CPTPCLoad, "TPC low written: TPC[{0}] ({1}) is now {2:x3}", _tpcAddr, (TaskType)_tpcAddr, _tpc[_tpcAddr]); + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected write to port {0:x2}", port)); + } + } + + public byte ReadPort(int port) + { + byte value = 0; + switch ((PortReadRegister)port) + { + case PortReadRegister.CPDataIn: + value = ReadCPOutBuffer(); + break; + + case PortReadRegister.CPStatus: + value = ReadCPStatus(); + break; + + case PortReadRegister.CPCS0: + case PortReadRegister.CPCS1: + case PortReadRegister.CPCS2: + case PortReadRegister.CPCS3: + case PortReadRegister.CPCS4: + case PortReadRegister.CPCS5: // CS microcode word, MSB ($F8) to LSB ($FD) + value = ReadIOPMicrocodeWord(port - 0xf8); + break; + + case PortReadRegister.CPCS6: // TPC high : TC[0:3],,TPCData[0:3]' + value = (byte)~((~_tc[_tpcAddr] << 4) | ((_tpc[_tpcAddr] & 0xf00) >> 8)); + break; + + case PortReadRegister.CPCS7: // TPC low : TPCData[4:11]' + value = (byte)(~_tpc[_tpcAddr]); + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected read from port {0:x2}", port)); + } + + if (Log.Enabled) Log.Write(LogComponent.CPControl, "CP port {0:x2}({1}) read {2:x2}", port, (PortReadRegister)port, value); + + return value; + } + + // + // IDMAInterface implementation + // + /// + /// DMA Request: device request to obtain a DMA cycle from the DMA controller + /// + public bool DRQ + { + get + { + if (_cpDmaMode) // DMA is enabled + { + if (_cpDmaIn) + { + return _outLatched; + } + else + { + return !_inLatched; + } + } + else + { + return false; + } + } + } + + /// + /// Writes a single byte to the device from the DMA controller + /// + /// + public void DMAWrite(byte value) + { + WriteCPInBuffer(value); + } + + /// + /// Reads a single byte from the device to the DMA controller + /// + /// + public byte DMARead() + { + return ReadCPOutBuffer(); + } + + public void DMAComplete() + { + _cpDmaComplete_ = true; + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "CP DMA complete, flag set."); + } + + private byte ReadCPOutBuffer() + { + if (!_outLatched) + { + if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.CPControl, "CP data out not latched on IOP read."); + } + + // + // Clear the output data latched flag. + // + _outLatched = false; + + // + // Clear the CP->IOP interrupt flag (active low): the IOP has read the available data. + // + _cpInIntReq_ = true; + + UpdateIOPTaskWakeup(); + + // + // Return the buffer data + // + return _cpInData; + } + + private void WriteCPInBuffer(byte value) + { + if (Log.Enabled) Log.Write(LogComponent.CPControl, "CP data out write ({0:x2})", value); + + if (_inLatched) + { + if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.CPControl, "CP data out already latched on IOP write", value); + } + + // + // Clear the IOP->CP interrupt (active low): The CP has yet to read the data provided by the IOP. + // + _cpOutIntReq_ = true; + _cpOutData = value; + + // + // Let the CP know there's data available. + // + _inLatched = true; + + UpdateIOPTaskWakeup(); + } + + /// + /// Writes one byte of the microcode word currently pointed to by + /// TPC[6]. + /// + /// + /// + private void WriteIOPMicrocodeWord(int b, byte value) + { + // + // In true Xerox fashion, the 48 bits of the microcode word provided by the IOP + // are not in order from MSB to LSB or anything simple like that. Though most of them are. + // From SysDefs.asm: + // + // "; Write (all CSi are complemented values): + // CSa equ CSBase + 0; CS Byte a: rA[0:3],,rB[0:3] + // CSb equ CSBase + 1; CS Byte b: aS[0:2],,aF[0:2],,aD[0:1] + // CSc equ CSBase + 2; CS Byte c: EP,,CIN,,EnSU,,mem,,fS[0:3] + // CSd equ CSBase + 3; CS Byte d: fY[0:3], INIA[0:3] + // CSe equ CSBase + 4; CS Byte e: fX[0:3], INIA[4:7] + // CSf equ CSBase + 5; CS Byte f: fZ[0:3], INIA[8:11]" + // + // "value" is expected to already be complemented on call to WriteIOPMicrocodeWord. + // + + + // TPC register 6 is always used for IOP microcode writes. + ulong word = _microcode[_tpc[6]]; + switch (b) + { + case 0: + word = (word & 0x00ffffffffff) | ((ulong)value << 40); + break; + + case 1: + word = (word & 0xff00ffffffff) | ((ulong)value << 32); + break; + + case 2: + word = (word & 0xffff00ffffff) | ((ulong)value << 24); + break; + + case 3: + { + // FY[0:3], INIA[0:3] + ulong fy = ((ulong)value & 0xf0) >> 4; + ulong inia = ((ulong)value & 0xf); + word = (word & 0xfffffff0f0ff) | (fy << 16) | (inia << 8); + } + break; + + case 4: + { + // FX[0:3], INIA[4:7] + ulong fx = ((ulong)value & 0xf0) >> 4; + ulong inia = ((ulong)value & 0xf); + word = (word & 0xffffff0fff0f) | (fx << 20) | (inia << 4); + } + break; + + case 5: + { + // FZ[0:3], INIA[8:11] + ulong fz = ((ulong)value & 0xf0) >> 4; + ulong inia = ((ulong)value & 0xf); + word = (word & 0xffffffff0ff0) | (fz << 12) | inia; + } + break; + + default: + throw new InvalidOperationException("Invalid byte number for microcode word."); + } + + _microcode[_tpc[6]] = word; + _microcodeCache[_tpc[6]] = new Microinstruction(word); + } + + /// + /// Reads one byte of the microcode word currently pointed to by + /// TPC[6]. + /// + /// + /// + private byte ReadIOPMicrocodeWord(int b) + { + byte value = 0; + + // TPC register 6 is always used for IOP microcode reads. + ulong word = _microcode[_tpc[6]]; + switch (b) + { + case 0: + value = (byte)(_microcode[_tpc[6]] >> 40); + break; + + case 1: + value = (byte)(_microcode[_tpc[6]] >> 32); + break; + + case 2: + value = (byte)(_microcode[_tpc[6]] >> 24); + break; + + case 3: + { + // FY[0:3], INIA[0:3] + value = (byte)(((_microcode[_tpc[6]] >> 12) & 0xf0) | ((_microcode[_tpc[6]] >> 8) & 0xf)); + } + break; + + case 4: + { + // FX[0:3], INIA[4:7] + value = (byte)(((_microcode[_tpc[6]] >> 16) & 0xf0) | ((_microcode[_tpc[6]] >> 4) & 0xf)); + } + break; + + case 5: + { + // FZ[0:3], INIA[8:11] + value = (byte)(((_microcode[_tpc[6]] >> 8) & 0xf0) | (_microcode[_tpc[6]] & 0xf)); + } + break; + + default: + throw new InvalidOperationException("Invalid byte number for microcode word."); + } + + return value; + } + + private void WriteCPCtl(byte value) + { + if (Log.Enabled) Log.Write(LogComponent.CPControl, "CP control write {0} ({1:x2})", (CPControlFlags)value, value); + + bool oldIopWait = _iopWait_; + _iopWait_ = (value & (int)CPControlFlags.IOPWait_) == 0; // inverted sense + _swTAddr = (value & (int)CPControlFlags.SwTAddr_) == 0; // ditto + _iopAttn = (value & (int)CPControlFlags.IOPattn) != 0; + _cpDmaMode = (value & (int)CPControlFlags.CPDmaMode) != 0; + _cpDmaIn = (value & (int)CPControlFlags.CPDmaIn) != 0; + + if (oldIopWait != _iopWait_) + { + // + // Raising IOPWait causes a Kernel wakeup to be requested so that the Kernel task + // will run when the CP is allowed to run again. + // + for(int i=0;i<7;i++) + { + SleepTask((TaskType)i); + } + + WakeTask(TaskType.Kernel); + _currentTask = TaskType.Kernel; + + // + // Reset any error status + // + _emulatorErrorTrap = false; + _emulatorErrorTrapClickCount = 0; + } + } + + private byte ReadCPStatus() + { + return (byte) + ((_cpDmaComplete_ ? CPStatusFlags.CPDmaComplete_ : 0) | + (!_cpOutIntReq_ ? CPStatusFlags.CPOutIntReq_ : 0) | + (!_cpInIntReq_ ? CPStatusFlags.CPInIntReq_ : 0) | + (!_cpDmaIn ? CPStatusFlags.CPDmaIn_ : 0) | + (!_cpDmaMode ? CPStatusFlags.CPDmaMode_ : 0) | + (_emuWake ? CPStatusFlags.EmuWake : 0) | + (!_cpAttn ? CPStatusFlags.CPAttn : 0)); + } + + private void WriteIOPCtl(byte value) + { + if (Log.Enabled) Log.Write(LogComponent.CPControl, "IOPCtl<- {0} ({1:x2})", (IOPCtlFlags)value, value); + + _wakeMode1 = (value & (int)IOPCtlFlags.WakeMode1) != 0; + _wakeMode0 = (value & (int)IOPCtlFlags.WakeMode0) != 0; + _cpAttn = (value & (int)IOPCtlFlags.CPAttn) != 0; + _emuWake = (value & (int)IOPCtlFlags.EmuWake) != 0; + + _wakeMode = (IOPTaskWakeMode)((_wakeMode0 ? 0x2 : 0x0) | (_wakeMode1 ? 0x1 : 0x0)); + if (Log.Enabled) Log.Write(LogComponent.CPControl, "IOP Wake mode is {0}", _wakeMode); + + // See if the wake status of the IOP task needs to change. + UpdateIOPTaskWakeup(); + } + + private byte ReadIOPStatus() + { + return (byte) + ((_iopReq ? IOPStatusFlags.IOPReq : 0) | + (!_wakeMode1 ? IOPStatusFlags.WakeMode1_ : 0) | + (!_wakeMode0 ? IOPStatusFlags.WakeMode0_ : 0) | + (!_cpAttn ? IOPStatusFlags.CPAttn_ : 0) | + (!_emuWake ? IOPStatusFlags.EmuWake_ : 0) | + (_iopAttn ? IOPStatusFlags.IOPAttn : 0)); + } + + private byte ReadIOPData() + { + if (!_inLatched) + { + if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.CPControl, "CP data not latched on <-IOPData."); + } + + // + // Clear the IOP request flag + // + _inLatched = false; + + // + // Raise the IOP->CP flag (active low): the CP has read the available data. + // + _cpOutIntReq_ = false; + + UpdateIOPTaskWakeup(); + + return _cpOutData; + } + + private void WriteIOPData(byte value) + { + if (_outLatched) + { + if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.CPControl, "CP data already latched on IOPOData<-"); + } + + // + // Latch the output data + // + _outLatched = true; + + // + // Raise the CP->IOP interrupt flag (active low): there is data available for the IOP to read. + // + _cpInIntReq_ = false; + + // + // Fill the CP->IOP buffer + // + _cpInData = value; + + UpdateIOPTaskWakeup(); + } + + private void UpdateIOPTaskWakeup() + { + // + // Wake or sleep the IOP task as appropriate based on the current wake mode + // and the status of the I/O channel. + // + switch (_wakeMode) + { + case IOPTaskWakeMode.Always: + // + // Like the label says, we wake up the IOP task unconditionally. + // + _iopReq = true; + WakeTask(TaskType.IOP); + break; + + case IOPTaskWakeMode.Input: + // + // If there's input waiting, wake the IOP task. + // + if (_inLatched) + { + _iopReq = true; + WakeTask(TaskType.IOP); + } + else + { + _iopReq = false; + SleepTask(TaskType.IOP); + } + break; + + case IOPTaskWakeMode.Output: + // + // If the output buffer is currently empty, wake the IOP task. + // + if (!_outLatched) + { + _iopReq = true; + WakeTask(TaskType.IOP); + } + else + { + _iopReq = false; + SleepTask(TaskType.IOP); + } + break; + + case IOPTaskWakeMode.Disabled: + _iopReq = false; + SleepTask(TaskType.IOP); + break; + } + } + + private enum PortReadRegister + { + CPDataIn = 0xeb, + CPStatus = 0xec, + CPCS0 = 0xf8, + CPCS1 = 0xf9, + CPCS2 = 0xfa, + CPCS3 = 0xfb, + CPCS4 = 0xfc, + CPCS5 = 0xfd, + CPCS6 = 0xfe, + CPCS7 = 0xff, + } + + private enum PortWriteRegister + { + CPDataOut = 0xeb, + CPControl = 0xec, + CPClrDmaComplete = 0xee, + CPCSa = 0xf8, + CPCSb = 0xf9, + CPCSc = 0xfa, + CPCSd = 0xfb, + CPCSe = 0xfc, + CPCSf = 0xfd, + TPCHigh = 0xfe, + TPCLow = 0xff, + } + + + // From the IOP schematic: + // - 00 = Disabled(no wakeups) + // - 01 = Input(wakeup when Input from IOP is available) + // - 10 = Output(wakeup when IOP is ready for data from CP) + // - 11 = Always wake up + private enum IOPTaskWakeMode + { + Disabled = 0, + Input, + Output, + Always, + } + + // + // IOP port data + // + private readonly int[] _readPorts = new int[] + { + (int)PortReadRegister.CPDataIn, // CP data in + (int)PortReadRegister.CPStatus, // CP port status + (int)PortReadRegister.CPCS0, // control store word, MSB + (int)PortReadRegister.CPCS1, + (int)PortReadRegister.CPCS2, + (int)PortReadRegister.CPCS3, + (int)PortReadRegister.CPCS4, + (int)PortReadRegister.CPCS5, // control store word, LSB + (int)PortReadRegister.CPCS6, // TPC high + (int)PortReadRegister.CPCS7, // low + }; + + private readonly int[] _writePorts = new int[] + { + (int)PortWriteRegister.CPDataOut, + (int)PortWriteRegister.CPControl, + (int)PortWriteRegister.CPClrDmaComplete, + (int)PortWriteRegister.CPCSa, // control store word, MSB + (int)PortWriteRegister.CPCSb, + (int)PortWriteRegister.CPCSc, + (int)PortWriteRegister.CPCSd, + (int)PortWriteRegister.CPCSe, + (int)PortWriteRegister.CPCSf, // control store word, LSB + (int)PortWriteRegister.TPCHigh, + (int)PortWriteRegister.TPCLow, + }; + + // + // Control data, IOP + // + private bool _cpDmaComplete_; + private bool _iopWait_; // Waiting for IOP to wake us + private bool _swTAddr; + private bool _iopAttn; + private bool _cpDmaMode; + private bool _cpDmaIn; + + // + // Control data, CP + // + private bool _wakeMode1; + private bool _wakeMode0; + private bool _cpAttn; + private bool _emuWake; + private IOPTaskWakeMode _wakeMode; + + // + // Status data + // + private bool _cpOutIntReq_; + private bool _cpInIntReq_; + private bool _outLatched; // Data from CP->IOP latched + private bool _inLatched; // Data from IOP->CP latched + private bool _iopReq; + + // + // CP<->IOP data buffers + // + private byte _cpOutData; // OUT from IOP (CP reads) + private byte _cpInData; // IN from CP (IOP reads) + + // Used as TPC address when IOP is writing control store or modifying TPC values. + private int _tpcAddr; + + // Temporary used when loading TPC values; stores high bits of new TPC address. + private int _tpcTemp; + } +} diff --git a/D/CP/MacroInstruction.cs b/D/CP/MacroInstruction.cs new file mode 100644 index 0000000..1fce3d3 --- /dev/null +++ b/D/CP/MacroInstruction.cs @@ -0,0 +1,641 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.CP +{ + public enum MacroType + { + Mesa, + Lisp, + } + + public enum MacroOperand + { + None, + Byte, + SignedByte, + Pair, + TwoByte, + Word, + ThreeByte, + } + /// + /// Provides facilities for interpreting Mesa bytecodes + /// + public struct MacroInstruction + { + private MacroInstruction(byte opcode, string mnemonic, MacroOperand operand) + { + _mnemonic = mnemonic; + _operand = operand; + } + + public string Mnemonic + { + get { return _mnemonic; } + } + + public MacroOperand Operand + { + get { return _operand; } + } + + public static MacroInstruction GetInstruction(MacroType type, byte opcode) + { + MacroInstruction inst = Invalid; + switch(type) + { + case MacroType.Mesa: + inst = _mesaInstructionTable[opcode]; + break; + + case MacroType.Lisp: + inst = _lispInstructionTable[opcode]; + break; + } + + return inst; + } + + private string _mnemonic; + private MacroOperand _operand; + + private static MacroInstruction[] _mesaInstructionTable = + { + Invalid, + new MacroInstruction(0x01, "LL0", MacroOperand.None), + new MacroInstruction(0x02, "LL1", MacroOperand.None), + new MacroInstruction(0x03, "LL2", MacroOperand.None), + new MacroInstruction(0x04, "LL3", MacroOperand.None), + new MacroInstruction(0x05, "LL4", MacroOperand.None), + new MacroInstruction(0x06, "LL5", MacroOperand.None), + new MacroInstruction(0x07, "LL6", MacroOperand.None), + new MacroInstruction(0x08, "LL7", MacroOperand.None), + new MacroInstruction(0x09, "LL8", MacroOperand.None), + new MacroInstruction(0x0a, "LL9", MacroOperand.None), + new MacroInstruction(0x0b, "LL10", MacroOperand.None), + new MacroInstruction(0x0c, "LL11", MacroOperand.None), + new MacroInstruction(0x0d, "LLB", MacroOperand.Byte), + new MacroInstruction(0x0e, "LLD0", MacroOperand.None), + new MacroInstruction(0x0f, "LLD1", MacroOperand.None), + + new MacroInstruction(0x10, "LLD2", MacroOperand.None), + new MacroInstruction(0x11, "LLD3", MacroOperand.None), + new MacroInstruction(0x12, "LLD4", MacroOperand.None), + new MacroInstruction(0x13, "LLD5", MacroOperand.None), + new MacroInstruction(0x14, "LLD6", MacroOperand.None), + new MacroInstruction(0x15, "LLD7", MacroOperand.None), + new MacroInstruction(0x16, "LLD8", MacroOperand.None), + new MacroInstruction(0x17, "LLD10", MacroOperand.None), + new MacroInstruction(0x18, "LLDB", MacroOperand.Byte), + new MacroInstruction(0x19, "SL0", MacroOperand.None), + new MacroInstruction(0x1a, "SL1", MacroOperand.None), + new MacroInstruction(0x1b, "SL2", MacroOperand.None), + new MacroInstruction(0x1c, "SL3", MacroOperand.None), + new MacroInstruction(0x1d, "SL4", MacroOperand.None), + new MacroInstruction(0x1e, "SL5", MacroOperand.None), + new MacroInstruction(0x1f, "SL6", MacroOperand.None), + + new MacroInstruction(0x20, "SL7", MacroOperand.None), + new MacroInstruction(0x21, "SL8", MacroOperand.None), + new MacroInstruction(0x22, "SL9", MacroOperand.None), + new MacroInstruction(0x23, "SL10", MacroOperand.None), + new MacroInstruction(0x24, "SLB", MacroOperand.Byte), + new MacroInstruction(0x25, "SLD0", MacroOperand.None), + new MacroInstruction(0x26, "SLD1", MacroOperand.None), + new MacroInstruction(0x27, "SLD2", MacroOperand.None), + new MacroInstruction(0x28, "SLD3", MacroOperand.None), + new MacroInstruction(0x29, "SLD4", MacroOperand.None), + new MacroInstruction(0x2a, "SLD5", MacroOperand.None), + new MacroInstruction(0x2b, "SLD6", MacroOperand.None), + new MacroInstruction(0x2c, "SLD8", MacroOperand.None), + new MacroInstruction(0x2d, "PL0", MacroOperand.None), + new MacroInstruction(0x2e, "PL1", MacroOperand.None), + new MacroInstruction(0x2f, "PL2", MacroOperand.None), + + new MacroInstruction(0x30, "PL3", MacroOperand.None), + new MacroInstruction(0x31, "PLB", MacroOperand.Byte), + new MacroInstruction(0x32, "PLD0", MacroOperand.None), + new MacroInstruction(0x33, "PLDB", MacroOperand.Byte), + new MacroInstruction(0x34, "LG0", MacroOperand.None), + new MacroInstruction(0x35, "LG1", MacroOperand.None), + new MacroInstruction(0x36, "LG2", MacroOperand.None), + new MacroInstruction(0x37, "LGB", MacroOperand.Byte), + new MacroInstruction(0x38, "LGD0", MacroOperand.None), + new MacroInstruction(0x39, "LGD2", MacroOperand.None), + new MacroInstruction(0x3a, "LGDB", MacroOperand.Byte), + new MacroInstruction(0x3b, "SGB", MacroOperand.Byte), + new MacroInstruction(0x3c, "BNDCK", MacroOperand.None), + new MacroInstruction(0x3d, "BRK", MacroOperand.None), + Invalid, + new MacroInstruction(0x3f, "STC", MacroOperand.None), + + new MacroInstruction(0x40, "R0", MacroOperand.None), + new MacroInstruction(0x41, "R1", MacroOperand.None), + new MacroInstruction(0x42, "RB", MacroOperand.Byte), + new MacroInstruction(0x43, "RL0", MacroOperand.None), + new MacroInstruction(0x44, "RLB", MacroOperand.Byte), + new MacroInstruction(0x45, "RD0", MacroOperand.None), + new MacroInstruction(0x46, "RDB", MacroOperand.None), + new MacroInstruction(0x47, "RDL0", MacroOperand.None), + new MacroInstruction(0x48, "RDLB", MacroOperand.Byte), + new MacroInstruction(0x49, "W0", MacroOperand.None), + new MacroInstruction(0x4a, "WB", MacroOperand.Byte), + new MacroInstruction(0x4b, "PSB", MacroOperand.Byte), + new MacroInstruction(0x4c, "WLB", MacroOperand.Byte), + new MacroInstruction(0x4d, "PSLB", MacroOperand.Byte), + new MacroInstruction(0x4e, "WDB", MacroOperand.Byte), + new MacroInstruction(0x4f, "PSD0", MacroOperand.None), + + new MacroInstruction(0x50, "PSDB", MacroOperand.Byte), + new MacroInstruction(0x51, "WDLB", MacroOperand.Byte), + new MacroInstruction(0x52, "PSDLB", MacroOperand.Byte), + new MacroInstruction(0x53, "RLI00", MacroOperand.None), + new MacroInstruction(0x54, "RLI01", MacroOperand.None), + new MacroInstruction(0x55, "RLI02", MacroOperand.None), + new MacroInstruction(0x56, "RLI03", MacroOperand.None), + new MacroInstruction(0x57, "RLIP", MacroOperand.Pair), + new MacroInstruction(0x58, "RLILP", MacroOperand.Pair), + new MacroInstruction(0x59, "RLDI00", MacroOperand.None), + new MacroInstruction(0x5a, "RLDIP", MacroOperand.Pair), + new MacroInstruction(0x5b, "RLDILP", MacroOperand.Pair), + new MacroInstruction(0x5c, "RGIP", MacroOperand.Pair), + new MacroInstruction(0x5d, "RGILP", MacroOperand.Pair), + new MacroInstruction(0x5e, "WLIP", MacroOperand.Pair), + new MacroInstruction(0x5f, "WLILP", MacroOperand.Pair), + + new MacroInstruction(0x60, "WLDILP", MacroOperand.Pair), + new MacroInstruction(0x61, "RS", MacroOperand.None), + new MacroInstruction(0x62, "RLS", MacroOperand.None), + new MacroInstruction(0x63, "WS", MacroOperand.None), + new MacroInstruction(0x64, "WLS", MacroOperand.None), + new MacroInstruction(0x65, "R0F", MacroOperand.Byte), + new MacroInstruction(0x66, "RF", MacroOperand.TwoByte), + new MacroInstruction(0x67, "RL0F", MacroOperand.Byte), + new MacroInstruction(0x68, "RLF", MacroOperand.TwoByte), + new MacroInstruction(0x69, "RLFS", MacroOperand.None), + new MacroInstruction(0x6a, "RLIPF", MacroOperand.Word), + new MacroInstruction(0x6b, "RLILPF", MacroOperand.Word), + new MacroInstruction(0x6c, "WOF", MacroOperand.None), + new MacroInstruction(0x6d, "WF", MacroOperand.TwoByte), + new MacroInstruction(0x6e, "PSF", MacroOperand.TwoByte), + new MacroInstruction(0x6f, "PS0F", MacroOperand.None), + + new MacroInstruction(0x70, "WS0F", MacroOperand.Byte), + new MacroInstruction(0x71, "WL0F", MacroOperand.Byte), + new MacroInstruction(0x72, "WLF", MacroOperand.TwoByte), + new MacroInstruction(0x73, "PSLF", MacroOperand.TwoByte), + new MacroInstruction(0x74, "WLFS", MacroOperand.Byte), + new MacroInstruction(0x75, "SLDB", MacroOperand.Byte), + new MacroInstruction(0x76, "SGDB", MacroOperand.Byte), + new MacroInstruction(0x77, "LLKB", MacroOperand.Byte), + new MacroInstruction(0x78, "RKIB", MacroOperand.Byte), + new MacroInstruction(0x79, "RKDIB", MacroOperand.Byte), + new MacroInstruction(0x7a, "LKB", MacroOperand.Byte), + new MacroInstruction(0x7b, "SHIFT", MacroOperand.None), + new MacroInstruction(0x7c, "SHIFTSB", MacroOperand.SignedByte), + new MacroInstruction(0x7d, "MBP", MacroOperand.Pair), + new MacroInstruction(0x7e, "RBP", MacroOperand.Pair), + new MacroInstruction(0x7f, "WBP", MacroOperand.Pair), + + new MacroInstruction(0x80, "CATCH", MacroOperand.Byte), + new MacroInstruction(0x81, "J2", MacroOperand.None), + new MacroInstruction(0x82, "J3", MacroOperand.None), + new MacroInstruction(0x83, "J4", MacroOperand.None), + new MacroInstruction(0x84, "J5", MacroOperand.None), + new MacroInstruction(0x85, "J6", MacroOperand.None), + new MacroInstruction(0x86, "J7", MacroOperand.None), + new MacroInstruction(0x87, "J8", MacroOperand.None), + new MacroInstruction(0x88, "JB", MacroOperand.Byte), + new MacroInstruction(0x89, "JW", MacroOperand.Word), + new MacroInstruction(0x8a, "JEP", MacroOperand.Pair), + new MacroInstruction(0x8b, "JEB", MacroOperand.Byte), + new MacroInstruction(0x8c, "JEBB", MacroOperand.TwoByte), + new MacroInstruction(0x8d, "JNEP", MacroOperand.Pair), + new MacroInstruction(0x8e, "JNEB", MacroOperand.Byte), + new MacroInstruction(0x8f, "JNEBB", MacroOperand.TwoByte), + + new MacroInstruction(0x90, "JLB", MacroOperand.Byte), + new MacroInstruction(0x91, "JGB", MacroOperand.Byte), + new MacroInstruction(0x92, "JGEB", MacroOperand.Byte), + new MacroInstruction(0x93, "JLEB", MacroOperand.Byte), + new MacroInstruction(0x94, "JULB", MacroOperand.Byte), + new MacroInstruction(0x95, "JUGB", MacroOperand.Byte), + new MacroInstruction(0x96, "JUGEB", MacroOperand.Byte), + new MacroInstruction(0x97, "JULEB", MacroOperand.Byte), + new MacroInstruction(0x98, "JZ3", MacroOperand.None), + new MacroInstruction(0x99, "JZ4", MacroOperand.None), + new MacroInstruction(0x9a, "JZB", MacroOperand.Byte), + new MacroInstruction(0x9b, "JNZ3", MacroOperand.None), + new MacroInstruction(0x9c, "JNZ4", MacroOperand.None), + new MacroInstruction(0x9d, "JNZB", MacroOperand.Byte), + new MacroInstruction(0x9e, "JDEB", MacroOperand.Byte), + new MacroInstruction(0x9f, "JDNEB", MacroOperand.Byte), + + new MacroInstruction(0xa0, "JIB", MacroOperand.Byte), + new MacroInstruction(0xa1, "JIW", MacroOperand.Word), + new MacroInstruction(0xa2, "REC", MacroOperand.None), + new MacroInstruction(0xa3, "REC2", MacroOperand.None), + new MacroInstruction(0xa4, "DIS", MacroOperand.None), + new MacroInstruction(0xa5, "DIS2", MacroOperand.None), + new MacroInstruction(0xa6, "EXCH", MacroOperand.None), + new MacroInstruction(0xa7, "DEXCH", MacroOperand.None), + new MacroInstruction(0xa8, "DUP", MacroOperand.None), + new MacroInstruction(0xa9, "DDUP", MacroOperand.None), + new MacroInstruction(0xaa, "EXDIS", MacroOperand.None), + new MacroInstruction(0xab, "NEG", MacroOperand.None), + new MacroInstruction(0xac, "INC", MacroOperand.None), + new MacroInstruction(0xad, "DEC", MacroOperand.None), + new MacroInstruction(0xae, "DINC", MacroOperand.None), + new MacroInstruction(0xaf, "DBL", MacroOperand.None), + + new MacroInstruction(0xb0, "DDBL", MacroOperand.None), + new MacroInstruction(0xb1, "TRPL", MacroOperand.None), + new MacroInstruction(0xb2, "AND", MacroOperand.None), + new MacroInstruction(0xb3, "IOR", MacroOperand.None), + new MacroInstruction(0xb4, "ADDSB", MacroOperand.SignedByte), + new MacroInstruction(0xb5, "ADD", MacroOperand.None), + new MacroInstruction(0xb6, "SUB", MacroOperand.None), + new MacroInstruction(0xb7, "DADD", MacroOperand.None), + new MacroInstruction(0xb8, "DSUB", MacroOperand.None), + new MacroInstruction(0xb9, "ADC", MacroOperand.None), + new MacroInstruction(0xba, "ACD", MacroOperand.None), + new MacroInstruction(0xbb, "AL0IB", MacroOperand.Byte), + new MacroInstruction(0xbc, "MUL", MacroOperand.None), + new MacroInstruction(0xbd, "DCMP", MacroOperand.None), + new MacroInstruction(0xbe, "UDCMP", MacroOperand.None), + Invalid, + + new MacroInstruction(0xc0, "LI0", MacroOperand.None), + new MacroInstruction(0xc1, "LI1", MacroOperand.None), + new MacroInstruction(0xc2, "LI2", MacroOperand.None), + new MacroInstruction(0xc3, "LI3", MacroOperand.None), + new MacroInstruction(0xc4, "LI4", MacroOperand.None), + new MacroInstruction(0xc5, "LI5", MacroOperand.None), + new MacroInstruction(0xc6, "LI6", MacroOperand.None), + new MacroInstruction(0xc7, "LI7", MacroOperand.None), + new MacroInstruction(0xc8, "LI8", MacroOperand.None), + new MacroInstruction(0xc9, "LI9", MacroOperand.None), + new MacroInstruction(0xca, "LI10", MacroOperand.None), + new MacroInstruction(0xcb, "LIN1", MacroOperand.None), + new MacroInstruction(0xcc, "LINI", MacroOperand.None), + new MacroInstruction(0xcd, "LIB", MacroOperand.Byte), + new MacroInstruction(0xce, "LIW", MacroOperand.Word), + new MacroInstruction(0xcf, "LINB", MacroOperand.Byte), + + new MacroInstruction(0xd0, "LIHB", MacroOperand.Byte), + new MacroInstruction(0xd1, "LID0", MacroOperand.None), + new MacroInstruction(0xd2, "LA0", MacroOperand.None), + new MacroInstruction(0xd3, "LA1", MacroOperand.None), + new MacroInstruction(0xd4, "LA2", MacroOperand.None), + new MacroInstruction(0xd5, "LA3", MacroOperand.None), + new MacroInstruction(0xd6, "LA6", MacroOperand.None), + new MacroInstruction(0xd7, "LA8", MacroOperand.None), + new MacroInstruction(0xd8, "LAB", MacroOperand.Byte), + new MacroInstruction(0xd9, "LAW", MacroOperand.Word), + new MacroInstruction(0xda, "GA0", MacroOperand.None), + new MacroInstruction(0xdb, "GA1", MacroOperand.None), + new MacroInstruction(0xdc, "GAB", MacroOperand.Byte), + new MacroInstruction(0xdd, "GAW", MacroOperand.Word), + Invalid, + new MacroInstruction(0xdf, "EFC0", MacroOperand.None), + + new MacroInstruction(0xe0, "EFC1", MacroOperand.None), + new MacroInstruction(0xe1, "EFC2", MacroOperand.None), + new MacroInstruction(0xe2, "EFC3", MacroOperand.None), + new MacroInstruction(0xe3, "EFC4", MacroOperand.None), + new MacroInstruction(0xe4, "EFC5", MacroOperand.None), + new MacroInstruction(0xe5, "EFC6", MacroOperand.None), + new MacroInstruction(0xe6, "EFC7", MacroOperand.None), + new MacroInstruction(0xe7, "EFC8", MacroOperand.None), + new MacroInstruction(0xe8, "EFC9", MacroOperand.None), + new MacroInstruction(0xe9, "EFC10", MacroOperand.None), + new MacroInstruction(0xea, "EFC11", MacroOperand.None), + new MacroInstruction(0xeb, "EFC12", MacroOperand.None), + new MacroInstruction(0xec, "EFCB", MacroOperand.Byte), + new MacroInstruction(0xed, "LFC", MacroOperand.Word), + new MacroInstruction(0xee, "SFC", MacroOperand.None), + new MacroInstruction(0xef, "RET", MacroOperand.None), + + new MacroInstruction(0xf0, "KFCB", MacroOperand.Byte), + new MacroInstruction(0xf1, "ME", MacroOperand.None), + new MacroInstruction(0xf2, "MX", MacroOperand.None), + new MacroInstruction(0xf3, "BLT", MacroOperand.None), + new MacroInstruction(0xf4, "BLTL", MacroOperand.None), + new MacroInstruction(0xf5, "BLTC", MacroOperand.None), + new MacroInstruction(0xf6, "BLTCL", MacroOperand.None), + new MacroInstruction(0xf7, "LP", MacroOperand.None), + new MacroInstruction(0xf8, "ESC", MacroOperand.Byte), + new MacroInstruction(0xf9, "ESCL", MacroOperand.Word), + new MacroInstruction(0xfa, "LGA0", MacroOperand.None), + new MacroInstruction(0xfb, "LGAB", MacroOperand.Byte), + new MacroInstruction(0xfc, "LGAW", MacroOperand.Word), + new MacroInstruction(0xfd, "DESC", MacroOperand.None), + Invalid, + Invalid, + }; + + private static MacroInstruction[] _lispInstructionTable = + { + Invalid, + new MacroInstruction(0x01, "CAR", MacroOperand.None), + new MacroInstruction(0x02, "CDR", MacroOperand.None), + new MacroInstruction(0x03, "LISTP", MacroOperand.None), + new MacroInstruction(0x04, "NTYPX", MacroOperand.None), + new MacroInstruction(0x05, "TYPEP", MacroOperand.Byte), + new MacroInstruction(0x06, "DTEST", MacroOperand.TwoByte), + new MacroInstruction(0x07, "UNWIND", MacroOperand.TwoByte), + new MacroInstruction(0x08, "FN0", MacroOperand.TwoByte), + new MacroInstruction(0x09, "FN1", MacroOperand.TwoByte), + new MacroInstruction(0x0a, "FN2", MacroOperand.TwoByte), + new MacroInstruction(0x0b, "FN3", MacroOperand.TwoByte), + new MacroInstruction(0x0c, "FN4", MacroOperand.TwoByte), + new MacroInstruction(0x0d, "FNX", MacroOperand.ThreeByte), + new MacroInstruction(0x0e, "APPLYFN", MacroOperand.None), + new MacroInstruction(0x0f, "CHECKAPPLY", MacroOperand.None), + + new MacroInstruction(0x10, "RETURN", MacroOperand.None), + new MacroInstruction(0x11, "BIND", MacroOperand.TwoByte), + new MacroInstruction(0x12, "UNBIND", MacroOperand.None), + new MacroInstruction(0x13, "DUNBIND", MacroOperand.None), + new MacroInstruction(0x14, "RPLPTR.N", MacroOperand.Byte), + new MacroInstruction(0x15, "GCREF", MacroOperand.Byte), + new MacroInstruction(0x16, "ASSOC", MacroOperand.None), + new MacroInstruction(0x17, "GVAR<-", MacroOperand.TwoByte), + new MacroInstruction(0x18, "RPLACA", MacroOperand.None), + new MacroInstruction(0x19, "RPLACD", MacroOperand.None), + new MacroInstruction(0x1a, "CONS", MacroOperand.None), + new MacroInstruction(0x1b, "CMLASSOC", MacroOperand.None), + new MacroInstruction(0x1c, "FMEMB", MacroOperand.None), + new MacroInstruction(0x1d, "CMLMEMBER", MacroOperand.None), + new MacroInstruction(0x1e, "FINDKEY", MacroOperand.Byte), + new MacroInstruction(0x1f, "CREATECELL", MacroOperand.None), + + new MacroInstruction(0x20, "BIN", MacroOperand.None), + new MacroInstruction(0x21, "BOUT", MacroOperand.None), + new MacroInstruction(0x22, "PROLOGOPDISP", MacroOperand.None), + new MacroInstruction(0x23, "RESTLIST", MacroOperand.Byte), + new MacroInstruction(0x24, "MISCN", MacroOperand.TwoByte), + new MacroInstruction(0x25, "ENDCOLLECT", MacroOperand.None), + new MacroInstruction(0x26, "RPLCONS", MacroOperand.None), + Invalid, + new MacroInstruction(0x28, "ELT", MacroOperand.None), + new MacroInstruction(0x29, "NTHCHC", MacroOperand.None), + new MacroInstruction(0x2a, "SETA", MacroOperand.None), + new MacroInstruction(0x2b, "RPLCHARCODE", MacroOperand.None), + new MacroInstruction(0x2c, "EVAL", MacroOperand.None), + new MacroInstruction(0x2d, "EVALV", MacroOperand.None), + new MacroInstruction(0x2e, "TYPECHECK.N", MacroOperand.Byte), + new MacroInstruction(0x2f, "STKSCAN", MacroOperand.None), + + new MacroInstruction(0x30, "BUSBLT", MacroOperand.Byte), + new MacroInstruction(0x31, "MISC8", MacroOperand.Byte), + new MacroInstruction(0x32, "UBFLOAT3", MacroOperand.Byte), + new MacroInstruction(0x33, "TYPEMASK.N", MacroOperand.Byte), + new MacroInstruction(0x34, "PROLOGREADPTR", MacroOperand.None), + new MacroInstruction(0x35, "PROLOGREADTAG", MacroOperand.None), + new MacroInstruction(0x36, "PROLOGWRITETAGPTR", MacroOperand.None), + new MacroInstruction(0x37, "PROLOGWRITE0PTR", MacroOperand.None), + new MacroInstruction(0x38, "PSEUDOCOLOR", MacroOperand.None), + Invalid, + new MacroInstruction(0x3a, "EQL", MacroOperand.None), + new MacroInstruction(0x3b, "DRAWLINE", MacroOperand.None), + new MacroInstruction(0x3c, "STORE.N", MacroOperand.Byte), + new MacroInstruction(0x3d, "COPY.N", MacroOperand.Byte), + new MacroInstruction(0x3e, "RAID", MacroOperand.None), + new MacroInstruction(0x3f, "\\RETURN", MacroOperand.None), + + new MacroInstruction(0x40, "IVAR0", MacroOperand.None), + new MacroInstruction(0x41, "IVAR1", MacroOperand.None), + new MacroInstruction(0x42, "IVAR2", MacroOperand.None), + new MacroInstruction(0x43, "IVAR3", MacroOperand.None), + new MacroInstruction(0x44, "IVAR4", MacroOperand.None), + new MacroInstruction(0x45, "IVAR5", MacroOperand.None), + new MacroInstruction(0x46, "IVAR6", MacroOperand.None), + new MacroInstruction(0x47, "IVARX", MacroOperand.Byte), + new MacroInstruction(0x48, "PVAR0", MacroOperand.None), + new MacroInstruction(0x49, "PVAR1", MacroOperand.None), + new MacroInstruction(0x4a, "PVAR2", MacroOperand.None), + new MacroInstruction(0x4b, "PVAR3", MacroOperand.None), + new MacroInstruction(0x4c, "PVAR4", MacroOperand.None), + new MacroInstruction(0x4d, "PVAR5", MacroOperand.None), + new MacroInstruction(0x4e, "PVAR6", MacroOperand.None), + new MacroInstruction(0x4f, "PVARX", MacroOperand.Byte), + + new MacroInstruction(0x50, "FVAR0", MacroOperand.None), + new MacroInstruction(0x51, "FVAR1", MacroOperand.None), + new MacroInstruction(0x52, "FVAR2", MacroOperand.None), + new MacroInstruction(0x53, "FVAR3", MacroOperand.None), + new MacroInstruction(0x54, "FVAR4", MacroOperand.None), + new MacroInstruction(0x55, "FVAR5", MacroOperand.None), + new MacroInstruction(0x56, "FVAR6", MacroOperand.None), + new MacroInstruction(0x57, "FVARX", MacroOperand.Byte), + new MacroInstruction(0x58, "PVAR0<-", MacroOperand.None), + new MacroInstruction(0x59, "PVAR1<-", MacroOperand.None), + new MacroInstruction(0x5a, "PVAR2<-", MacroOperand.None), + new MacroInstruction(0x5b, "PVAR3<-", MacroOperand.None), + new MacroInstruction(0x5c, "PVAR4<-", MacroOperand.None), + new MacroInstruction(0x5d, "PVAR5<-", MacroOperand.None), + new MacroInstruction(0x5e, "PVAR6<-", MacroOperand.None), + new MacroInstruction(0x5f, "PVARX<-", MacroOperand.Byte), + + new MacroInstruction(0x60, "GVAR", MacroOperand.TwoByte), + new MacroInstruction(0x61, "ARG0", MacroOperand.None), + new MacroInstruction(0x62, "IVARX<-", MacroOperand.Byte), + new MacroInstruction(0x63, "FVARX<-", MacroOperand.Byte), + new MacroInstruction(0x64, "COPY", MacroOperand.None), + new MacroInstruction(0x65, "MYARGCOUNT", MacroOperand.None), + new MacroInstruction(0x66, "MYALINK", MacroOperand.None), + new MacroInstruction(0x67, "ACONST", MacroOperand.TwoByte), + new MacroInstruction(0x68, "'NIL", MacroOperand.None), + new MacroInstruction(0x69, "'T", MacroOperand.None), + new MacroInstruction(0x6a, "'0", MacroOperand.None), + new MacroInstruction(0x6b, "'1", MacroOperand.None), + new MacroInstruction(0x6c, "SIC", MacroOperand.Byte), + new MacroInstruction(0x6d, "SNIC", MacroOperand.Byte), + new MacroInstruction(0x6e, "SICX", MacroOperand.TwoByte), + new MacroInstruction(0x6f, "GCONST", MacroOperand.ThreeByte), + + new MacroInstruction(0x70, "ATOMNUMBER", MacroOperand.TwoByte), + new MacroInstruction(0x71, "READFLAGS", MacroOperand.None), + new MacroInstruction(0x72, "READRP", MacroOperand.None), + new MacroInstruction(0x73, "WRITEMAP", MacroOperand.None), + new MacroInstruction(0x74, "READPRINTERPORT", MacroOperand.None), + new MacroInstruction(0x75, "WRITEPRINTERPORT", MacroOperand.None), + new MacroInstruction(0x76, "PILOTBITBLT", MacroOperand.None), + new MacroInstruction(0x77, "RCLK", MacroOperand.None), + new MacroInstruction(0x78, "MISC1", MacroOperand.Byte), + new MacroInstruction(0x79, "MISC2", MacroOperand.Byte), + new MacroInstruction(0x7a, "RECLAIMCELL", MacroOperand.None), + new MacroInstruction(0x7b, "GCSCAN1", MacroOperand.None), + new MacroInstruction(0x7c, "GCSCAN2", MacroOperand.None), + new MacroInstruction(0x7d, "SUBRCALL", MacroOperand.TwoByte), + new MacroInstruction(0x7e, "CONTEXT", MacroOperand.None), + Invalid, + + new MacroInstruction(0x80, "JUMP0", MacroOperand.None), + new MacroInstruction(0x81, "JUMP1", MacroOperand.None), + new MacroInstruction(0x82, "JUMP2", MacroOperand.None), + new MacroInstruction(0x83, "JUMP3", MacroOperand.None), + new MacroInstruction(0x84, "JUMP4", MacroOperand.None), + new MacroInstruction(0x85, "JUMP5", MacroOperand.None), + new MacroInstruction(0x86, "JUMP6", MacroOperand.None), + new MacroInstruction(0x87, "JUMP7", MacroOperand.None), + new MacroInstruction(0x88, "JUMP8", MacroOperand.None), + new MacroInstruction(0x89, "JUMP9", MacroOperand.None), + new MacroInstruction(0x8a, "JUMP10", MacroOperand.None), + new MacroInstruction(0x8b, "JUMP11", MacroOperand.None), + new MacroInstruction(0x8c, "JUMP12", MacroOperand.None), + new MacroInstruction(0x8d, "JUMP13", MacroOperand.None), + new MacroInstruction(0x8e, "JUMP14", MacroOperand.None), + new MacroInstruction(0x8f, "JUMP15", MacroOperand.None), + + new MacroInstruction(0x90, "FJUMP0", MacroOperand.None), + new MacroInstruction(0x91, "FJUMP1", MacroOperand.None), + new MacroInstruction(0x92, "FJUMP2", MacroOperand.None), + new MacroInstruction(0x93, "FJUMP3", MacroOperand.None), + new MacroInstruction(0x94, "FJUMP4", MacroOperand.None), + new MacroInstruction(0x95, "FJUMP5", MacroOperand.None), + new MacroInstruction(0x96, "FJUMP6", MacroOperand.None), + new MacroInstruction(0x97, "FJUMP7", MacroOperand.None), + new MacroInstruction(0x98, "FJUMP8", MacroOperand.None), + new MacroInstruction(0x99, "FJUMP9", MacroOperand.None), + new MacroInstruction(0x9a, "FJUMP10", MacroOperand.None), + new MacroInstruction(0x9b, "FJUMP11", MacroOperand.None), + new MacroInstruction(0x9c, "FJUMP12", MacroOperand.None), + new MacroInstruction(0x9d, "FJUMP13", MacroOperand.None), + new MacroInstruction(0x9e, "FJUMP14", MacroOperand.None), + new MacroInstruction(0x9f, "FJUMP15", MacroOperand.None), + + new MacroInstruction(0xa0, "TJUMP2", MacroOperand.None), + new MacroInstruction(0xa1, "TJUMP3", MacroOperand.None), + new MacroInstruction(0xa2, "TJUMP4", MacroOperand.None), + new MacroInstruction(0xa3, "TJUMP5", MacroOperand.None), + new MacroInstruction(0xa4, "TJUMP6", MacroOperand.None), + new MacroInstruction(0xa5, "TJUMP7", MacroOperand.None), + new MacroInstruction(0xa6, "TJUMP8", MacroOperand.None), + new MacroInstruction(0xa7, "TJUMP9", MacroOperand.None), + new MacroInstruction(0xa8, "TJUMPa", MacroOperand.None), + new MacroInstruction(0xa9, "TJUMPb", MacroOperand.None), + new MacroInstruction(0xaa, "TJUMPc", MacroOperand.None), + new MacroInstruction(0xab, "TJUMPe", MacroOperand.None), + new MacroInstruction(0xac, "TJUMPf", MacroOperand.None), + new MacroInstruction(0xad, "TJUMP10", MacroOperand.None), + new MacroInstruction(0xae, "TJUMP11", MacroOperand.None), + new MacroInstruction(0xaf, "TJUMP12", MacroOperand.None), + + new MacroInstruction(0xb0, "JUMPX", MacroOperand.Byte), + new MacroInstruction(0xb1, "JUMPXX", MacroOperand.TwoByte), + new MacroInstruction(0xb2, "FJUMPX", MacroOperand.Byte), + new MacroInstruction(0xb3, "TJUMPX", MacroOperand.Byte), + new MacroInstruction(0xb4, "NFJUMPX", MacroOperand.Byte), + new MacroInstruction(0xb5, "NTJUMPX", MacroOperand.Byte), + new MacroInstruction(0xb6, "AREF1", MacroOperand.None), + new MacroInstruction(0xb7, "ASET1", MacroOperand.None), + new MacroInstruction(0xb8, "PVAR0<-^", MacroOperand.None), + new MacroInstruction(0xb9, "PVAR1<-^", MacroOperand.None), + new MacroInstruction(0xba, "PVAR2<-^", MacroOperand.None), + new MacroInstruction(0xbb, "PVAR3<-^", MacroOperand.None), + new MacroInstruction(0xbc, "PVAR4<-^", MacroOperand.None), + new MacroInstruction(0xbd, "PVAR5<-^", MacroOperand.None), + new MacroInstruction(0xbe, "PVAR6<-^", MacroOperand.None), + new MacroInstruction(0xbf, "POP", MacroOperand.None), + + new MacroInstruction(0xc0, "POP.N", MacroOperand.Byte), + new MacroInstruction(0xc1, "ATOMCELL.N", MacroOperand.Byte), + new MacroInstruction(0xc2, "GETBASEBYTE", MacroOperand.None), + new MacroInstruction(0xc3, "INSTANCEP", MacroOperand.TwoByte), + new MacroInstruction(0xc4, "BLT", MacroOperand.None), + new MacroInstruction(0xc5, "MISC10", MacroOperand.TwoByte), + Invalid, + new MacroInstruction(0xc7, "PUTBASEBYTE", MacroOperand.None), + new MacroInstruction(0xc8, "GETBASE.N", MacroOperand.Byte), + new MacroInstruction(0xc9, "GETBASEPTR.N", MacroOperand.Byte), + new MacroInstruction(0xca, "GETBITS.N.FD", MacroOperand.TwoByte), + Invalid, + new MacroInstruction(0xcc, "CMLEQUAL", MacroOperand.None), + new MacroInstruction(0xcd, "PUTBASE.N", MacroOperand.Byte), + new MacroInstruction(0xce, "PUTBASEPTR.N", MacroOperand.Byte), + new MacroInstruction(0xcf, "PUTBITS.N.FD", MacroOperand.TwoByte), + + new MacroInstruction(0xd0, "ADDBASE", MacroOperand.None), + new MacroInstruction(0xd1, "VAG2", MacroOperand.None), + new MacroInstruction(0xd2, "HILOC", MacroOperand.None), + new MacroInstruction(0xd3, "LOLOC", MacroOperand.None), + new MacroInstruction(0xd4, "PLUS2", MacroOperand.None), + new MacroInstruction(0xd5, "DIFFERENCE", MacroOperand.None), + new MacroInstruction(0xd6, "TIMES2", MacroOperand.None), + new MacroInstruction(0xd7, "QUOTIENT", MacroOperand.None), + new MacroInstruction(0xd8, "IPLUS2", MacroOperand.None), + new MacroInstruction(0xd9, "IDIFFERENCE", MacroOperand.None), + new MacroInstruction(0xda, "ITIMES2", MacroOperand.None), + new MacroInstruction(0xdb, "IQUOTIENT", MacroOperand.None), + new MacroInstruction(0xdc, "IREMAINDER", MacroOperand.None), + new MacroInstruction(0xdd, "IPLUS.N", MacroOperand.Byte), + new MacroInstruction(0xde, "IDIFFERENCE.N", MacroOperand.Byte), + Invalid, + + new MacroInstruction(0xe0, "LLSH1", MacroOperand.None), + new MacroInstruction(0xe1, "LLSH8", MacroOperand.None), + new MacroInstruction(0xe2, "LRSH1", MacroOperand.None), + new MacroInstruction(0xe3, "LRSH8", MacroOperand.None), + new MacroInstruction(0xe4, "LOGOR2", MacroOperand.None), + new MacroInstruction(0xe5, "LOGAND2", MacroOperand.None), + new MacroInstruction(0xe6, "LOGXOR2", MacroOperand.None), + new MacroInstruction(0xe7, "LSH", MacroOperand.None), + new MacroInstruction(0xe8, "FPLUS2", MacroOperand.None), + new MacroInstruction(0xe9, "FDIFFERENCE", MacroOperand.None), + new MacroInstruction(0xea, "FTIMES2", MacroOperand.None), + new MacroInstruction(0xeb, "FQUOTIENT", MacroOperand.None), + new MacroInstruction(0xec, "UBFLOAT2", MacroOperand.Byte), + new MacroInstruction(0xed, "UBFLOAT1", MacroOperand.Byte), + new MacroInstruction(0xee, "AREF2", MacroOperand.None), + new MacroInstruction(0xef, "ASET2", MacroOperand.None), + + new MacroInstruction(0xe0, "EQ", MacroOperand.None), + new MacroInstruction(0xe1, "IGREATERP", MacroOperand.None), + new MacroInstruction(0xe2, "FGREATERP", MacroOperand.None), + new MacroInstruction(0xe3, "GREATERP", MacroOperand.None), + new MacroInstruction(0xe4, "EQUAL", MacroOperand.None), + new MacroInstruction(0xe5, "MAKENUMBER", MacroOperand.None), + new MacroInstruction(0xe6, "BOXIPLUS", MacroOperand.None), + new MacroInstruction(0xe7, "BOXIDIFFERENCE", MacroOperand.None), + new MacroInstruction(0xe8, "FLOATBLT", MacroOperand.None), + new MacroInstruction(0xe9, "FFTSTEP", MacroOperand.None), + new MacroInstruction(0xea, "MISC3", MacroOperand.ThreeByte), + new MacroInstruction(0xeb, "MISC4", MacroOperand.ThreeByte), + Invalid, + new MacroInstruction(0xed, "SWAP", MacroOperand.None), + new MacroInstruction(0xee, "NOP", MacroOperand.None), + Invalid, + }; + + private static MacroInstruction Invalid = new MacroInstruction(0x00, "INVALID", MacroOperand.None); + } +} diff --git a/D/CP/Microinstruction.cs b/D/CP/Microinstruction.cs new file mode 100644 index 0000000..111637f --- /dev/null +++ b/D/CP/Microinstruction.cs @@ -0,0 +1,1267 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; + +namespace D.CP +{ + public enum AluSourcePair + { + AQ = 0, + AB = 1, + ZQ = 2, + ZB = 3, + ZA = 4, + DA = 5, + DQ = 6, + D0 = 7, + } + + public enum AluFunction + { + RplusS = 0, + SminusR = 1, + RminusS = 2, + RorS = 3, + RandS = 4, + notRandS = 5, + RxorS = 6, + notRxorS = 7, + } + + public enum FunctionSelectFY + { + DispBr = 0, + fyNorm = 1, + IOOut = 2, + Byte = 3, + } + + public enum FunctionSelectFZ + { + fzNorm = 0, + Nibble = 1, + Uaddr = 2, + IOXIn = 3, + } + + public enum XFunction + { + pCallRet0 = 0x0, + pCallRet1 = 0x1, + pCallRet2 = 0x2, + pCallRet3 = 0x3, + pCallRet4 = 0x4, + pCallRet5 = 0x5, + pCallRet6 = 0x6, + pCallRet7 = 0x7, + Noop = 0x8, + LoadRH = 0x9, + shift = 0xa, + cycle = 0xb, + LoadCinFrompc16 = 0xc, + LoadMap = 0xd, + pop = 0xe, + push = 0xf, + } + + public enum YNormFunction + { + ExitKern = 0x0, + EnterKern = 0x1, + ClrIntErr = 0x2, + IBDisp = 0x3, + MesaIntRq = 0x4, + LoadstackP = 0x5, + LoadIB = 0x6, + cycle = 0x7, + Noop = 0x8, + LoadMap = 0x9, + Refresh = 0xa, + push = 0xb, + ClrDPRq = 0xc, + ClrIOPRq = 0xd, + ClrRefRq = 0xe, + ClrKFlags = 0xf, + } + + public enum YDispBrFunction + { + NegBr = 0x0, + ZeroBr = 0x1, + NZeroBr = 0x2, + MesaIntBr = 0x3, + PgCarryBr = 0x4, + CarryBr = 0x5, + XRefBr = 0x6, + NibCarryBr = 0x7, + XDisp = 0x8, + YDisp = 0x9, + XC2npcDisp = 0xa, + YIODisp = 0xb, + XwdDisp = 0xc, + XHDisp = 0xd, + XLDisp = 0xe, // AKA XDirtyDisp + PgCrOvDisp = 0xf, + } + + public enum YIOOutFunction + { + IOPOData = 0x0, + IOPCtl = 0x1, + KOData = 0x2, + KCtl = 0x3, + EOData = 0x4, + EICtl = 0x5, + DCtlFifo = 0x6, + DCtl = 0x7, + DBorder = 0x8, + PCtl = 0x9, + MCtl = 0xa, + Invalid0 = 0xb, + EOCtl = 0xc, + KCmd = 0xd, + Invalid1 = 0xe, + POData = 0xf, + } + + public enum ZNormFunction + { + Refresh = 0x0, + LoadIBPtr1 = 0x1, + LoadIBPtr0 = 0x2, + LoadCinFrompc16 = 0x3, + LoadBank = 0x4, + pop = 0x5, + push = 0x6, + AltUaddr = 0x7, + Noop0 = 0x8, + Noop1 = 0x9, + Noop2 = 0xa, + Noop3 = 0xb, + LRot0 = 0xc, + LRot12 = 0xd, + LRot8 = 0xe, + LRot4 = 0xf + } + + // For Zap Rowsdower + public enum ZIOXIn + { + ReadEIdata = 0x0, + ReadEStatus = 0x1, + ReadKIData = 0x2, + ReadKStatus = 0x3, + KStrobe = 0x4, + ReadMStatus = 0x5, + ReadKTest = 0x6, + EStrobe = 0x7, + ReadIOPIData = 0x8, + ReadIOPStatus = 0x9, + ReadErrnIBnStkp = 0xa, + ReadRH = 0xb, + ReadibNA = 0xc, + Readib = 0xd, + ReadibLow = 0xe, + ReadibHigh = 0xf, + } + + public enum StackTestType + { + None, + Underflow, + Overflow, + Underflow2, + } + + /// + /// Decodes a single microcode word. + /// + public class Microinstruction + { + public Microinstruction(ulong word) + { + rA = (int)((word & 0xf00000000000) >> 44); + rB = (int)((word & 0x0f0000000000) >> 40); + aS = (AluSourcePair)((word & 0x00e000000000) >> 37); + aF = (AluFunction)((word & 0x001c00000000) >> 34); + aD = (int)((word & 0x000300000000) >> 32); + ep = (word & 0x000080000000) != 0; + Cin = (word & 0x000040000000) != 0; + enSU = (word & 0x000020000000) != 0; + mem = (word & 0x000010000000) != 0; + fSfY = (FunctionSelectFY)((word & 0x00000c000000) >> 26); + fSfZ = (FunctionSelectFZ)((word & 0x000003000000) >> 24); + fX = (XFunction)((word & 0x000000f00000) >> 20); + fY = (int)((word & 0x0000000f0000) >> 16); + fZ = (int)((word & 0x00000000f000) >> 12); + INIA = (int)((word & 0x000000000fff)); + + + // + // Instruction metadata that can be precomputed and cached + // + Cycle = (fX == XFunction.cycle) || + (fSfY == FunctionSelectFY.fyNorm && ((YNormFunction)fY) == YNormFunction.cycle); + Shift = + ((fX == XFunction.shift) || + Cycle); + + AluNeedsXBus = (aS == AluSourcePair.D0 || aS == AluSourcePair.DA || aS == AluSourcePair.DQ); + + AluDestination = aD | (Shift ? 0x4 : 0x0); + + SURead = enSU && !Cin; + + SUWrite = enSU && Cin; + + LoadMap = fX == XFunction.LoadMap || + (fSfY == FunctionSelectFY.fyNorm && + (YNormFunction)fY == YNormFunction.LoadMap); + + ABypass = AluDestination == 0x2; + + LoadStackP = (fSfY == FunctionSelectFY.fyNorm && + (YNormFunction)fY == YNormFunction.LoadstackP); + + LoadIBPtr1 = (fSfZ == FunctionSelectFZ.fzNorm && ((ZNormFunction)fZ) == ZNormFunction.LoadIBPtr1); + + AlwaysIBDisp = (fSfY == FunctionSelectFY.fyNorm && + (YNormFunction)fY == YNormFunction.IBDisp) && + LoadIBPtr1; + + LoadIB = fSfY == FunctionSelectFY.fyNorm && + (YNormFunction)fY == YNormFunction.LoadIB; + + UAddress = (rA << 4) | fZ; + + switch (fX) + { + case XFunction.pCallRet0: + case XFunction.pCallRet1: + case XFunction.pCallRet2: + case XFunction.pCallRet3: + case XFunction.pCallRet4: + case XFunction.pCallRet5: + case XFunction.pCallRet6: + case XFunction.pCallRet7: + LinkAddress = (int)fX; + break; + + default: + LinkAddress = -1; + break; + } + + MarMapMDR = mem || LoadMap; + + LateLRotN = !ABypass && fSfZ == FunctionSelectFZ.fzNorm; + + if (fSfY == FunctionSelectFY.Byte) + { + // Byte constant + Byte = (byte)((fY << 4) | fZ); + } + else if (fSfZ == FunctionSelectFZ.Nibble) + { + // Nibble constant + Byte = (byte)fZ; + } + else + { + // No constant value. + Byte = 0; + } + + bool fxPop = (fX == XFunction.pop); + bool fzPop = (fSfZ == FunctionSelectFZ.fzNorm && ((ZNormFunction)fZ) == ZNormFunction.pop); + + Pop = fxPop || fzPop; + + // + // There is a special case if both fxPop and fzPop are specified: stackP is still decremented by 1, + // but a trap is invoked if stackP is 1 or 0 (rather than just 0). + // + DoublePop = fxPop && fzPop; + + Push = (fX == XFunction.push) || + (fSfY == FunctionSelectFY.fyNorm && ((YNormFunction)fY) == YNormFunction.push) || + (fSfZ == FunctionSelectFZ.fzNorm && ((ZNormFunction)fZ) == ZNormFunction.push); + + StackOperation = Pop || Push; + + // + // From the HWref (p. 33): + // "Multiple pop's and push's can be specified per microinstruction in order to ameliorate the detection + // of Stack overflow or underflow. For instance, fXpop (i.e. the pop in the fX field), fZpop, and + // push executed together leave the stackPointer unmodified, yet simulate two pop's with respect to + // stack underflow detection..." + // The actual overflow detection logic is controlled by a PROM, there's nothing too weird going on + // here (other than overloading to provide only semi-related semantics, which is annoying.) At + // any rate, we precompute the check that's being requested (if any) so we don't have to do it + // at execution time. + // TODO: might make sense to dump the PROM and use that. + // + if (fxPop && fzPop && Push) + { + StackTest = StackTestType.Underflow2; + } + else if (Push && fzPop) + { + StackTest = StackTestType.Overflow; + } + else if (fxPop && Push) + { + StackTest = StackTestType.Underflow; + } + else + { + // No non-modify test, just normal stack behavior. + StackTest = StackTestType.None; + } + + } + + /// + /// 2901 A reg addr, U addr [0-3] + /// + public readonly int rA; + + /// + /// 2901 B reg addr, RH addr + /// + public readonly int rB; + + /// + /// 2901 alu Source operand pair + /// + public readonly AluSourcePair aS; + + /// + /// 2901 alu Function + /// + public readonly AluFunction aF; + + /// + /// 2901 alu Destination/shift control + /// + public readonly int aD; + + /// + /// Even Parity + /// + public readonly bool ep; + + /// + /// 2901 Carry In, Shift Ends, writeSU (if enSU = 1) + /// + public readonly bool Cin; + + /// + /// enable SU reg file + /// + public readonly bool enSU; + + /// + /// MAR<- (if c1), MDR<- (if c2), <-MD (if c3) + /// + public readonly bool mem; + + /// + /// Function field selector for Y + /// + public readonly FunctionSelectFY fSfY; + + /// + /// Function field selector for Z + /// + public readonly FunctionSelectFZ fSfZ; + + /// + /// X Function + /// + public readonly XFunction fX; + + /// + /// Y Function + /// + public readonly int fY; + + /// + /// Z Function + /// + public readonly int fZ; + + /// + /// Next Instruction Address + /// + public readonly int INIA; + + // + // The following are metadata for this instruction, used to speed execution. + // + + /// + /// Instruction specifies a Cycle of ALU output when writing back to R/Q + /// + public readonly bool Cycle; + + /// + /// Instruction specifies a Shift of ALU as above. + /// + public readonly bool Shift; + + /// + /// Instruction requires XBus input to ALU. + /// + public readonly bool AluNeedsXBus; + + /// + /// Destination control for the ALU + /// + public readonly int AluDestination; + + /// + /// Instruction specifies an SU register read + /// + public readonly bool SURead; + + /// + /// Instruction specifies an SU register write + /// + public readonly bool SUWrite; + + /// + /// Instruction specifies a Map<- operation. + /// + public readonly bool LoadMap; + + /// + /// Instruction uses the A-bypass mode for the ALU. + /// + public readonly bool ABypass; + + /// + /// Instruction specifies a stackP<- operation. + /// + public readonly bool LoadStackP; + + /// + /// Instruction specifies a push operation + /// + public readonly bool Push; + + /// + /// Instruction specifies a pop operation + /// + public readonly bool Pop; + + /// + /// Instruction specifies a double-pop operation. + /// + public readonly bool DoublePop; + + /// + /// Whether any stack operations (pushes or pops) occur in this instruction. + /// + public readonly bool StackOperation; + + /// + /// Specifies the kind of test specified by the various + /// push/pop instruction fields. + /// + public readonly StackTestType StackTest; + + /// + /// Causes an IBDisp branch even if IB is not full; + /// specified by IBDisp + IBPtr<-1 + /// + public readonly bool AlwaysIBDisp; + + /// + /// Whether an IB<- is specified this instruction. + /// + public readonly bool LoadIB; + + /// + /// Whether the instruction specifies an ibPtr<-1 operation, + /// which can be used to modify other operations. + /// + public readonly bool LoadIBPtr1; + + /// + /// Constant address used to address U register when loading/storing + /// + public readonly int UAddress; + + /// + /// Constant byte value + /// + /// + public readonly byte Byte; + + /// + /// Link address specified by instruction (or -1 if not specified) + /// + public readonly int LinkAddress; + + /// + /// Whether the instruction specifies an MAR<-, Map<-, or MDR<- operation. + /// + public readonly bool MarMapMDR; + + /// + /// Whether to do an LrotN operation after the ALU runs. + /// + public readonly bool LateLRotN; + + public override string ToString() + { + return String.Format("rA={0:x} rB={1:x} aS={2} aF={3} aD={4} ep={5} Cin={6} enSU={7} mem={8} fSY={9} fSZ={10} fX={11} fY={12:x} fZ={13:x} INIA={14:x3}", + rA, rB, aS, aF, aD, ep, Cin, enSU, mem, fSfY, fSfZ, fX, fY, fZ, INIA); + } + + public string Disassemble(int cycle) + { + // + // Build ALU op, start with the sources: + // + + string aluR; + string aluS; + bool Rzero = false; + bool Szero = false; + + string xBusValue = DisassembleXBusSource(cycle); + + switch (aS) + { + case AluSourcePair.AB: + aluR = String.Format("R{0:x}", rA); + aluS = String.Format("R{0:x}", rB); + break; + + case AluSourcePair.AQ: + aluR = String.Format("R{0:x}", rA); + aluS = "Q"; + break; + + case AluSourcePair.ZA: + aluR = "0"; + Rzero = true; + aluS = String.Format("R{0:x}", rA); + break; + + case AluSourcePair.ZB: + aluR = "0"; + Rzero = true; + aluS = String.Format("R{0:x}", rB); + break; + + case AluSourcePair.ZQ: + aluR = "0"; + Rzero = true; + aluS = "Q"; + break; + + case AluSourcePair.D0: + aluR = xBusValue; + aluS = "0"; + Szero = true; + break; + + case AluSourcePair.DA: + aluR = xBusValue; + aluS = String.Format("R{0:x}", rA); + break; + + case AluSourcePair.DQ: + aluR = xBusValue; + aluS = "Q"; + break; + + default: + throw new InvalidOperationException("Unexpected ALU source pair."); + } + + // + // Select operation + // + string aluOp; + switch (aF) + { + case AluFunction.RplusS: + if (Rzero) + { + aluOp = aluS; + } + else if (Szero) + { + aluOp = aluR; + } + else + { + aluOp = String.Format("{0}+{1}", aluR, aluS); + } + break; + + case AluFunction.SminusR: + if (Rzero) + { + aluOp = aluS; + } + else if (Szero) + { + aluOp = "-" + aluR; + } + else + { + aluOp = String.Format("{0}-{1}", aluS, aluR); + } + break; + + case AluFunction.RminusS: + if (Rzero) + { + aluOp = "-" + aluS; + } + else if (Szero) + { + aluOp = aluR; + } + else + { + aluOp = String.Format("{0}-{1}", aluR, aluS); + } + break; + + case AluFunction.RorS: + if (Rzero) + { + aluOp = aluS; + } + else if (Szero) + { + aluOp = aluR; + } + else + { + aluOp = String.Format("{0} or {1}", aluR, aluS); + } + break; + + case AluFunction.RandS: + if (Rzero) + { + aluOp = "0"; + } + else if (Szero) + { + aluOp = "0"; + } + else + { + aluOp = String.Format("{0} and {1}", aluR, aluS); + } + break; + + case AluFunction.notRandS: + if (Rzero) + { + aluOp = aluS; + } + else if (Szero) + { + aluOp = "0"; + } + else + { + aluOp = String.Format("~{0} and {1}", aluR, aluS); + } + break; + + case AluFunction.RxorS: + if (Rzero) + { + aluOp = aluS; + } + else if (Szero) + { + aluOp = aluR; + } + else + { + aluOp = String.Format("{0} xor {1}", aluR, aluS); + } + break; + + case AluFunction.notRxorS: + if (Szero) + { + aluOp = "~" + aluR; + } + else + { + aluOp = String.Format("~{0} xor {1}", aluR, aluS); + } + break; + + default: + throw new InvalidOperationException("Unexpected ALU operation"); + } + + // + // Select register writeback (to rB) + // Q writeback, and Y source (F, or A bypass) + // + int writeFn = aD | (Shift ? 0x4 : 0x0); + string regAssignment; + bool aBypass = false; + bool yBusIsSourceForDestination = false; + bool aluNoWriteBack = false; + switch(writeFn) + { + case 0: + // no write, Q<-F + regAssignment = String.Format("Q<- {0}{1}", aluOp, GetCarryMod()); + yBusIsSourceForDestination = true; + break; + + case 1: + // no write. + regAssignment = String.Format("{0}{1}", aluOp, GetCarryMod()); + yBusIsSourceForDestination = false; + aluNoWriteBack = true; + break; + + case 2: + // R[rB] <- F, no write to Q, A Bypass for YBus<- + regAssignment = String.Format("R{0:x}<- {1}{2}", rB, aluOp, GetCarryMod()); + aBypass = true; + yBusIsSourceForDestination = true; + break; + + case 3: + // R[rB] <- F, no write to Q + regAssignment = String.Format("R{0:x}<- {1}{2}", rB, aluOp, GetCarryMod()); + yBusIsSourceForDestination = true; + break; + + case 4: + if (Cycle) + { + // double-word right shift + regAssignment = String.Format("R{0:x}<- DRShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + else + { + // double-word arithmetic right shift. + regAssignment = String.Format("R{0:x}<- DARShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + yBusIsSourceForDestination = true; + break; + + case 5: + if (Cycle) + { + // F: single-word right rotate: + regAssignment = String.Format("R{0:x}<- RRot1 {1}{2}", rB, aluOp, GetCarryMod()); + } + else + { + // F: single-word right shift w/carryIn to MSB: + regAssignment = String.Format("R{0:x}<- RShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + yBusIsSourceForDestination = true; + break; + + case 6: + if (Cycle) + { + // double-word left shift + regAssignment = String.Format("R{0:x}<- DLShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + else + { + // double-word arithmetic left shift + regAssignment = String.Format("R{0:x}<- DALShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + yBusIsSourceForDestination = true; + break; + + case 7: + if (Cycle) + { + // single-word left rotate: + regAssignment = String.Format("R{0:x}<- LRot1 {1}{2}", rB, aluOp, GetCarryMod()); + } + else + { + // single-word left shift w/carryIn to MSB: + regAssignment = String.Format("R{0:x}<- LShift1 {1}{2}{3}", rB, aluOp, GetCarryMod(), Cin ? " SE<-1" : String.Empty); + } + yBusIsSourceForDestination = true; + break; + + default: + throw new InvalidOperationException("Unexpected sh,,aD value."); + } + + string yBusValue = aBypass ? String.Format("R{0:x}, {1}", rA, regAssignment) : String.Format("{0}", regAssignment); + + string fxFunc = String.Empty; + bool xBusIsSourceForDestination = false; + + bool yBusBranch = false; + bool xBusBranch = false; + + // Handle fX functions that aren't implicitly handled elsewhere (shift, cycle) + switch (fX) + { + case XFunction.pCallRet0: + case XFunction.pCallRet1: + case XFunction.pCallRet2: + case XFunction.pCallRet3: + case XFunction.pCallRet4: + case XFunction.pCallRet5: + case XFunction.pCallRet6: + case XFunction.pCallRet7: + fxFunc = String.Format("pCall/Ret{0} ", (int)fX); + break; + + case XFunction.LoadRH: + fxFunc = String.Format("RH{0:x}<-", rB); + xBusIsSourceForDestination = true; + break; + + case XFunction.LoadCinFrompc16: + fxFunc = "SE<-pc16 "; + break; + + case XFunction.LoadMap: + fxFunc = String.Format("Map<- RH{0:x},,", rB); + yBusIsSourceForDestination = true; + break; + + case XFunction.pop: + fxFunc = "pop "; + break; + + case XFunction.push: + fxFunc = "push "; + break; + } + + string fyFunc = String.Empty; + + // Handle fY functions that aren't implicitly handled elsewhere (cycle, Byte, etc.) + switch (fSfY) + { + case FunctionSelectFY.fyNorm: + switch ((YNormFunction)fY) + { + case YNormFunction.ExitKern: + fyFunc = "ExitKern "; + break; + + case YNormFunction.EnterKern: + fyFunc = "EnterKern "; + break; + + case YNormFunction.ClrIntErr: + fyFunc = "ClrIntErr "; + break; + + case YNormFunction.IBDisp: + fyFunc = "IBDisp "; + break; + + case YNormFunction.MesaIntRq: + fyFunc = "MesaIntRq "; + break; + + case YNormFunction.LoadstackP: + fyFunc = "stackP<-"; + yBusIsSourceForDestination = true; + break; + + case YNormFunction.LoadIB: + fyFunc = "IB<-"; + xBusIsSourceForDestination = true; + break; + + case YNormFunction.LoadMap: + fyFunc = String.Format("Map<- RH{0:x},,", rB); + break; + + case YNormFunction.Refresh: + fyFunc = "Refresh "; + break; + + case YNormFunction.push: + fyFunc = "push "; + break; + + case YNormFunction.ClrDPRq: + fyFunc = "ClrDPRq "; + break; + + case YNormFunction.ClrIOPRq: + fyFunc = "ClrIOPRq "; + break; + + case YNormFunction.ClrRefRq: + fyFunc = "ClrRefRq "; + break; + + case YNormFunction.ClrKFlags: + fyFunc = "ClrKFlags "; + break; + } + break; + + case FunctionSelectFY.DispBr: + fyFunc = ((YDispBrFunction)fY).ToString() + " "; + + switch ((YDispBrFunction)fY) + { + case YDispBrFunction.NegBr: + case YDispBrFunction.ZeroBr: + case YDispBrFunction.NibCarryBr: + case YDispBrFunction.PgCarryBr: + case YDispBrFunction.CarryBr: + case YDispBrFunction.PgCrOvDisp: + case YDispBrFunction.YDisp: + case YDispBrFunction.YIODisp: + yBusBranch = true; + break; + + case YDispBrFunction.XRefBr: + case YDispBrFunction.XwdDisp: + case YDispBrFunction.XHDisp: + case YDispBrFunction.XLDisp: + case YDispBrFunction.XDisp: + case YDispBrFunction.XC2npcDisp: + xBusBranch = true; + break; + } + + break; + + case FunctionSelectFY.IOOut: + if (fY != 0xb && fY != 0xe) + { + YIOOutFunction yIOOut = ((YIOOutFunction)fY); + fyFunc = yIOOut.ToString() + "<-"; + + // IOOut functions are roughly split between taking data from the XBus or the YBus. + xBusIsSourceForDestination = + (yIOOut == YIOOutFunction.IOPOData || + yIOOut == YIOOutFunction.IOPCtl || + yIOOut == YIOOutFunction.KOData || + yIOOut == YIOOutFunction.KCtl || + yIOOut == YIOOutFunction.EOData || + yIOOut == YIOOutFunction.EICtl || + yIOOut == YIOOutFunction.DCtl || + yIOOut == YIOOutFunction.PCtl || + yIOOut == YIOOutFunction.EOCtl || + yIOOut == YIOOutFunction.KCmd || + yIOOut == YIOOutFunction.POData + ); + + yBusIsSourceForDestination = (!xBusIsSourceForDestination && (fY != 0xb && fY != 0xe)); + + } + break; + } + + string fzFunc = String.Empty; + + // Handle fZ functions that aren't implicitly handled elsewhere (IOXIn) + switch (fSfZ) + { + case FunctionSelectFZ.fzNorm: + switch((ZNormFunction)fZ) + { + case ZNormFunction.Refresh: + fzFunc = "Refresh "; + break; + + case ZNormFunction.LoadIBPtr1: + fzFunc = "IBPtr<-1 "; + break; + + case ZNormFunction.LoadIBPtr0: + fzFunc = "IBPtr<-0 "; + break; + + case ZNormFunction.LoadCinFrompc16: + fzFunc = "SE<-pc16 "; + break; + + case ZNormFunction.pop: + fzFunc = "pop "; + break; + + case ZNormFunction.push: + fzFunc = "push "; + break; + + case ZNormFunction.AltUaddr: + fzFunc = "AltUaddr "; + break; + + case ZNormFunction.LRot0: + fzFunc = "LRot0 "; + break; + + case ZNormFunction.LRot12: + fzFunc = "LRot12 "; + break; + + case ZNormFunction.LRot8: + fzFunc = "LRot8 "; + break; + + case ZNormFunction.LRot4: + fzFunc = "LRot4 "; + break; + } + break; + } + + // SU reg write + string suWriteStr = String.Empty; + bool suWrite = enSU && Cin; + bool suRead = enSU && !Cin; + if (suWrite) + { + switch((int)fSfZ) + { + case 0: + case 1: + suWriteStr = "STK<-"; + break; + + case 2: + case 3: + suWriteStr = String.Format("U{0:x2}<-", (rA << 4) | fZ); + break; + } + + yBusIsSourceForDestination = true; + } + + if(!yBusIsSourceForDestination && !xBusIsSourceForDestination) + { + suWriteStr = "Xbus<- "; + + // Y bus is implicitly used to provide an X bus value if nothing else is selected. + yBusIsSourceForDestination = string.IsNullOrEmpty(xBusValue); + } + + // MAR or MDR writes: + string memWrite = String.Empty; + + if (mem) + { + if (cycle == 1) + { + memWrite = "MAR<- "; + } + else if (cycle == 2) + { + memWrite = "MDR<- "; + } + else if (cycle == -1) + { + memWrite = "{MAR/MDR/MD} "; + } + } + + // + // The below is kind of messy because of conflation of the ALU with the Y-Bus way up above, etc. + // Bear with me. + // + + // + // The Y Bus value is important and needs to be included in the disassembly if one or more of the + // below are true: + // - A register assignment is taking place + // - The Y Bus is being used as a data source + // - A dispatch or branch involving the Y Bus or ALU is being invoked during this instruction. + // + bool showyBusValue = (yBusBranch || yBusIsSourceForDestination || !aluNoWriteBack); + + // + // The X Bus value is important and needs to be included in the disassembly if one or more of the + // below are true: + // - The ALU isn't already using the X Bus as an input + // - The X Bus is being used as a data source + // - A dispatch or branch involving the X Bus is being invoked during this instruction. + // + bool showxBusValue = (xBusBranch || !AluNeedsXBus || xBusIsSourceForDestination); + + string disassembly = String.Format("{0}{1}{2}{3}{4}{5}{6} [{7:x3}]", + fxFunc, + fyFunc, + fzFunc, + memWrite, + suWriteStr, + showxBusValue ? xBusValue : String.Empty, + showyBusValue ? yBusValue : String.Empty, + INIA); + + + return disassembly; + } + + private string GetCarryMod() + { + string mod = String.Empty; + bool add = (aF == AluFunction.RplusS); + bool sub = (aF == AluFunction.RminusS || aF == AluFunction.SminusR); + + if (Cin & add) + { + mod = "+1"; + } + else if (!Cin & sub) + { + mod = "-1"; + } + + return mod; + } + + private string DisassembleXBusSource(int cycle) + { + string xBus = String.Empty; + + // Byte and/or Nibble. In theory these are mutually exclusive, + // but there's nothing that prevents them both from being coded at the same time. + // If this happens, Byte takes precedence. + if (fSfY == FunctionSelectFY.Byte) + { + xBus = String.Format("byte({0:x2})", ((fY << 4) | fZ)); + } + + if(fSfZ == FunctionSelectFZ.Nibble && fSfY != FunctionSelectFY.Byte) + { + xBus = String.Format("nibble({0:x1})", fZ); + } + else if (fSfZ == FunctionSelectFZ.IOXIn) + { + // IOXIn sources + switch((ZIOXIn)fZ) + { + case ZIOXIn.ReadEIdata: + xBus += "EIData"; + break; + + case ZIOXIn.ReadEStatus: + xBus += "EStatus"; + break; + + case ZIOXIn.ReadKIData: + xBus += "KIData"; + break; + + case ZIOXIn.ReadKStatus: + xBus += "KStatus"; + break; + + case ZIOXIn.ReadMStatus: + xBus += "MStatus"; + break; + + case ZIOXIn.ReadKTest: + xBus += "KTest"; + break; + + case ZIOXIn.ReadIOPIData: + xBus += "IOPIData"; + break; + + case ZIOXIn.ReadIOPStatus: + xBus += "IOPStatus"; + break; + + case ZIOXIn.ReadErrnIBnStkp: + xBus += "ErrnIBnStkP"; + break; + + case ZIOXIn.ReadRH: + xBus += String.Format("RH{0:x}", rB); + break; + + case ZIOXIn.ReadibNA: + xBus += "ibNA"; + break; + + case ZIOXIn.ReadibLow: + xBus += "ibLow"; + break; + + case ZIOXIn.ReadibHigh: + xBus += "ibHigh"; + break; + + default: + xBus += ((ZIOXIn)fZ).ToString(); + break; + } + } + + if (enSU && !Cin) // Cin is 0 for reads + { + // SU read operations + switch((int)fSfZ) + { + case 0: + case 1: + xBus += "STK"; + break; + + case 2: + case 3: + xBus += String.Format("U{0:x2}", (rA << 4) | fZ); + break; + } + } + + if (mem && cycle == 3) + { + xBus += "<-MD"; + } + + return xBus; + } + } +} diff --git a/D/CP/Source/BootKernel_map.txt b/D/CP/Source/BootKernel_map.txt new file mode 100644 index 0000000..aca29a4 --- /dev/null +++ b/D/CP/Source/BootKernel_map.txt @@ -0,0 +1,43 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[BootKernel.mc,v] +*none*: 0x0fdf,50 +KGo: 0x0fde,49 +*none*: 0x0fdd,51 +*none*: 0x0fdc,52 +*none*: 0x0fdb,53 +*none*: 0x0fda,54 +*none*: 0x0fd9,55 +*none*: 0x0fd8,56 +KEntry: 0x0fe0,66 +*none*: 0x0fe2,67 +*none*: 0x0fe6,68 +*none*: 0x0fe8,69 +KRefresh: 0x0fe5,71 +*none*: 0x0fe9,72 +*none*: 0x0fea,73 +KLoop: 0x0fe4,75 +*none*: 0x0feb,76 +*none*: 0x0ff0,77 +KTable: 0x0ffc,88 +*none*: 0x0fee,89 +*none*: 0x0fef,90 +*none*: 0x0fe7,81 +*none*: 0x0fe1,80 +*none*: 0x0ff1,82 +*none*: 0x0fec,85 +*none*: 0x0fed,86 +KDisp: 0x0fe3,84 +KRefCmd: 0x0ffd,92 +KCmd2: 0x0ffe,93 +KCmd3: 0x0fff,94 + diff --git a/D/CP/Source/CPMemTest_map.txt b/D/CP/Source/CPMemTest_map.txt new file mode 100644 index 0000000..1399d21 --- /dev/null +++ b/D/CP/Source/CPMemTest_map.txt @@ -0,0 +1,256 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonCPMemTest.mc,v] +*none*: 0x00ff,72 +Start: 0x0182,71 +*none*: 0x0183,73 +*none*: 0x0184,75 +*none*: 0x0185,76 +*none*: 0x0186,77 +*none*: 0x0187,79 +*none*: 0x0188,80 +*none*: 0x0189,81 +*none*: 0x018a,83 +*none*: 0x018b,84 +*none*: 0x018c,85 +walk: 0x018d,90 +ChkBound: 0x018e,91 +*none*: 0x018f,92 +*none*: 0x0190,94 +*none*: 0x0096,95 +ChkYTop: 0x0191,97 +YBotOK: 0x0097,96 +*none*: 0x0192,99 +YTopOK: 0x0068,101 +ChkDone: 0x0193,102 +StartM: 0x0194,105 +*none*: 0x0195,106 +*none*: 0x0196,107 +*none*: 0x0197,109 +*none*: 0x0198,110 +*none*: 0x0199,111 +*none*: 0x019a,113 +*none*: 0x019b,114 +*none*: 0x019c,115 +*none*: 0x019d,117 +RUNLIST: 0x0098,120 +RUNLISTX: 0x0060,121 +SETUP: 0x01ea,293 +*none*: 0x01eb,294 +*none*: 0x01ec,295 +*none*: 0x01ed,297 +*none*: 0x01ee,298 +*none*: 0x01ef,299 +*none*: 0x01f0,301 +*none*: 0x01f1,302 +*none*: 0x01f2,303 +*none*: 0x01f3,305 +*none*: 0x01f4,306 +*none*: 0x01f5,307 +*none*: 0x01f6,308 +*none*: 0x01f7,311 +*none*: 0x01f8,312 +*none*: 0x01f9,313 +*none*: 0x01fa,314 +*none*: 0x01fb,315 +*none*: 0x01fc,318 +*none*: 0x01fd,319 +*none*: 0x01fe,321 +*none*: 0x01ff,322 +*none*: 0x0200,323 +*none*: 0x0201,325 +RUNLISTRET: 0x01cf,241 +*none*: 0x01d0,242 +*none*: 0x0038,243 +*none*: 0x00e0,123 +RUNL: 0x019e,124 +*none*: 0x019f,125 +RunTest: 0x0050,127 +*none*: 0x0051,128 +*none*: 0x0052,129 +*none*: 0x0053,130 +RUNLIST1: 0x01a0,133 +*none*: 0x0061,134 +*none*: 0x00e1,136 +*none*: 0x01a1,137 +*none*: 0x0022,138 +SSTORE: 0x0202,345 +*none*: 0x0025,346 +AdjustQ: 0x0227,480 +*none*: 0x0228,481 +*none*: 0x001c,483 +*none*: 0x001d,484 +*none*: 0x001e,485 +*none*: 0x001f,486 +*none*: 0x0105,347 +*none*: 0x0203,349 +*none*: 0x0046,350 +*none*: 0x0106,351 +*none*: 0x0204,353 +*none*: 0x0205,354 +STYPE: 0x0058,356 +*none*: 0x0059,357 +*none*: 0x005a,358 +*none*: 0x005b,359 +*none*: 0x005c,360 +SSTORE3: 0x020e,384 +*none*: 0x020f,385 +*none*: 0x0210,386 +*none*: 0x0211,387 +SDOAGAIN: 0x0084,388 +SLMATCH: 0x0085,391 +*none*: 0x0212,389 +SSTORE1: 0x0206,364 +*none*: 0x0207,365 +STYPE1: 0x0078,367 +*none*: 0x0079,368 +*none*: 0x007a,369 +*none*: 0x007b,370 +*none*: 0x007c,371 +*none*: 0x0027,392 +*none*: 0x0107,393 +*none*: 0x0213,394 +*none*: 0x0214,395 +*none*: 0x0215,397 +*none*: 0x0008,398 +*none*: 0x00d4,401 +SUMATCH: 0x00d5,403 +*none*: 0x0108,402 +*none*: 0x00e2,140 +*none*: 0x01a2,141 +Delay1: 0x01a3,143 +*none*: 0x01a4,144 +Delay2: 0x006b,146 +*none*: 0x006a,145 +*none*: 0x01a5,147 +*none*: 0x01a6,148 +*none*: 0x009c,149 +RUNL1: 0x009d,151 +*none*: 0x01a7,152 +RunTest1: 0x0070,154 +*none*: 0x0071,155 +*none*: 0x0072,156 +*none*: 0x0073,157 +RUNLIST2: 0x01a8,160 +*none*: 0x0023,161 +*none*: 0x00e3,164 +*none*: 0x01a9,165 +*none*: 0x0024,166 +CHECK: 0x0216,421 +*none*: 0x0081,422 +*none*: 0x0101,423 +*none*: 0x0217,425 +*none*: 0x0218,426 +*none*: 0x0028,428 +*none*: 0x0029,429 +*none*: 0x002a,430 +*none*: 0x002b,431 +*none*: 0x002c,432 +CHECK2: 0x021e,446 +*none*: 0x021f,447 +*none*: 0x0220,448 +CHECKENTRY: 0x0221,450 +*none*: 0x0222,451 +*none*: 0x0086,452 +DATAERR: 0x0087,471 +*none*: 0x0223,454 +CDOAGAIN: 0x00d6,455 +CLMATCH: 0x00d7,458 +*none*: 0x0224,456 +CHECK1: 0x0219,434 +*none*: 0x021a,435 +CTYPE1: 0x00d8,437 +*none*: 0x00d9,438 +*none*: 0x00da,439 +*none*: 0x00db,440 +*none*: 0x00dc,441 +*none*: 0x0225,459 +*none*: 0x0226,462 +*none*: 0x0042,463 +CDOAGAIN1: 0x0088,466 +CUMATCH: 0x0089,469 +*none*: 0x0102,467 +*none*: 0x00e4,168 +*none*: 0x01aa,169 +*none*: 0x01ab,170 +RUNLIST4X: 0x00e6,184 +*none*: 0x01b2,185 +*none*: 0x01b3,186 +*none*: 0x01b4,188 +TYPE0: 0x006c,190 +TYPE1: 0x006d,191 +TYPE3: 0x01b5,193 +DATAINC: 0x01b6,197 +*none*: 0x01b7,198 +*none*: 0x01b8,199 +*none*: 0x01b9,201 +*none*: 0x01ba,202 +*none*: 0x01bb,203 +*none*: 0x01bc,205 +*none*: 0x01bd,206 +*none*: 0x01be,207 +*none*: 0x01bf,208 +*none*: 0x01c0,209 +CHECKPASS: 0x01c1,212 +*none*: 0x01c2,213 +PASSDONE1: 0x0099,216 +*none*: 0x00e7,172 +*none*: 0x01ac,173 +*none*: 0x01ad,174 +*none*: 0x01ae,177 +*none*: 0x01af,178 +*none*: 0x0026,179 +MAP: 0x0229,498 +*none*: 0x022a,499 +*none*: 0x022b,500 +MAPOFF: 0x00f0,502 +MAPON: 0x00f1,505 +MDOAGAIN: 0x00f2,517 +*none*: 0x0233,518 +*none*: 0x0234,520 +*none*: 0x0235,521 +*none*: 0x0236,522 +*none*: 0x0237,524 +*none*: 0x0238,525 +*none*: 0x0239,526 +MAP2: 0x022d,508 +*none*: 0x022e,509 +*none*: 0x022f,510 +MAPENTRY: 0x0230,512 +*none*: 0x0231,513 +*none*: 0x008a,514 +MDATAERR: 0x008b,543 +*none*: 0x0232,516 +MLMATCH: 0x00f3,529 +*none*: 0x023a,530 +*none*: 0x023b,532 +*none*: 0x023c,533 +MDOAGAIN1: 0x008c,536 +MUMATCH: 0x008d,540 +*none*: 0x023d,537 +*none*: 0x021b,442 +*none*: 0x021c,443 +*none*: 0x021d,444 +TERROR1: 0x00df,63 +*none*: 0x0208,372 +*none*: 0x0209,373 +*none*: 0x020a,374 +SSTORE2: 0x020b,380 +*none*: 0x020c,381 +*none*: 0x020d,382 + +[MoonCPMemDisplay.mc,v] +DisplayStart: 0x0030,81 + +[MoonCPMemKernel.mc,v] +KernelStart: 0x003c,40 + diff --git a/D/CP/Source/FloppyInitial_map.txt b/D/CP/Source/FloppyInitial_map.txt new file mode 100644 index 0000000..9fcca64 --- /dev/null +++ b/D/CP/Source/FloppyInitial_map.txt @@ -0,0 +1,263 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[CoreInitial.mc,v] +*none*: 0x01f2,179 +*none*: 0x01f1,178 +*none*: 0x01f0,177 +*none*: 0x01ef,176 +*none*: 0x01f3,180 +*none*: 0x01f4,181 +largeVMOK: 0x01f5,187 +*none*: 0x01f6,188 +*none*: 0x01f7,189 +*none*: 0x01f8,191 +*none*: 0x010a,192 +*none*: 0x01f9,193 +*none*: 0x01ee,171 +*none*: 0x01ec,169 +*none*: 0x01ed,170 +*none*: 0x01eb,166 +*none*: 0x01ea,165 +*none*: 0x01e9,164 +mapBuild: 0x01e8,163 +go: 0x01b1,51 +*none*: 0x012f,52 +*none*: 0x01b2,55 +*none*: 0x01b3,66 +*none*: 0x01b4,67 +*none*: 0x01b5,68 +*none*: 0x01b6,69 +*none*: 0x01b7,70 +*none*: 0x01b8,71 +*none*: 0x01d8,140 +mapInit: 0x01d7,139 +*none*: 0x01d9,141 +*none*: 0x01da,142 +*none*: 0x01db,143 +*none*: 0x01dc,144 +*none*: 0x01dd,145 +*none*: 0x01de,146 +mark1: 0x01e0,151 +*none*: 0x01e1,152 +*none*: 0x011c,153 +*none*: 0x01e2,154 +*none*: 0x0108,155 +*none*: 0x01e3,156 +*none*: 0x01e4,157 +*none*: 0x01e5,158 +*none*: 0x01e6,159 +*none*: 0x01e7,160 +markPages: 0x0109,149 +*none*: 0x01df,150 +BLT: 0x0231,324 +*none*: 0x0232,325 +*none*: 0x0233,326 +*none*: 0x0228,299 +*none*: 0x0229,300 +*none*: 0x0124,298 +*none*: 0x0125,302 +*none*: 0x022a,303 +*none*: 0x022b,304 +clearLoop: 0x0134,290 +*none*: 0x0135,306 +*none*: 0x01b9,77 +*none*: 0x01ba,78 +*none*: 0x01bb,79 +*none*: 0x01bc,80 +*none*: 0x01bd,81 +clear: 0x0114,83 +*none*: 0x01be,84 +*none*: 0x01bf,85 +*none*: 0x0115,87 +*none*: 0x01c0,88 +*none*: 0x01c1,89 +*none*: 0x01c2,94 +*none*: 0x01c3,95 +*none*: 0x01c4,98 +*none*: 0x01c5,99 +*none*: 0x01c6,100 +*none*: 0x01c7,101 +*none*: 0x01c8,103 +*none*: 0x01c9,104 +*none*: 0x01ca,105 +SetVMMSize: 0x01cb,107 +*none*: 0x01cc,108 +*none*: 0x01cd,109 +*none*: 0x01ce,111 +*none*: 0x01cf,112 +enableIOP: 0x01d0,113 +*none*: 0x01d1,115 +*none*: 0x01d2,116 +*none*: 0x01d3,117 +*none*: 0x01d4,120 +*none*: 0x01d5,121 +*none*: 0x01d6,122 + +[InitDLion.mc,v] +OnceOnlyINit: 0x0101,66 +*none*: 0x010b,67 +*none*: 0x010e,78 +*none*: 0x011a,79 +*none*: 0x010f,89 +*none*: 0x011e,90 +*none*: 0x0128,92 +*none*: 0x0129,93 +*none*: 0x012b,94 +*none*: 0x012c,96 +*none*: 0x012d,97 +*none*: 0x012e,98 +*none*: 0x013a,100 +*none*: 0x013b,101 +*none*: 0x013c,102 +*none*: 0x013d,104 +*none*: 0x013e,105 +*none*: 0x013f,106 +*none*: 0x0140,108 +*none*: 0x0141,109 +*none*: 0x0142,110 +*none*: 0x0143,112 +*none*: 0x0144,113 +*none*: 0x0145,114 +*none*: 0x0146,116 +*none*: 0x0147,117 +*none*: 0x0148,118 +*none*: 0x0149,120 +*none*: 0x014a,121 +*none*: 0x014b,122 +IOPageLoop: 0x0111,126 +*none*: 0x014c,127 +*none*: 0x014d,128 +EtherInit: 0x0113,131 +*none*: 0x014e,132 +*none*: 0x014f,133 +*none*: 0x0150,135 +*none*: 0x0151,136 +*none*: 0x0152,137 +*none*: 0x0153,139 +*none*: 0x0154,140 +*none*: 0x0155,141 +MagTapeInit: 0x0156,144 +*none*: 0x0157,145 +*none*: 0x0158,146 +DiskInit: 0x0159,149 +*none*: 0x015a,150 +SAx000: 0x0117,153 +*none*: 0x015b,156 +*none*: 0x015c,157 +*none*: 0x015d,158 +*none*: 0x015e,161 +*none*: 0x015f,162 +*none*: 0x0160,163 +*none*: 0x0161,165 +SetSA1Const: 0x0102,177 +*none*: 0x0165,178 +*none*: 0x0166,181 +*none*: 0x0167,182 +SetURegs: 0x0168,183 +*none*: 0x0169,186 +*none*: 0x016a,187 +*none*: 0x016b,188 +DisplayInit: 0x018d,273 +*none*: 0x018e,274 +*none*: 0x018f,275 +*none*: 0x0190,277 +*none*: 0x0191,278 +*none*: 0x0192,279 +*none*: 0x0193,281 +*none*: 0x0194,282 +*none*: 0x0195,283 +*none*: 0x0196,285 +*none*: 0x0197,286 +*none*: 0x0198,287 +*none*: 0x0199,289 +*none*: 0x019a,290 +*none*: 0x019b,291 +*none*: 0x019c,293 +*none*: 0x019d,294 +*none*: 0x019e,295 +*none*: 0x019f,297 +*none*: 0x01a0,298 +*none*: 0x01a1,299 +*none*: 0x01a2,301 +*none*: 0x01a3,302 +*none*: 0x01a4,303 +*none*: 0x01a5,305 +*none*: 0x01a6,306 +*none*: 0x01a7,307 +*none*: 0x01a8,309 +*none*: 0x01a9,310 +LSepInit: 0x01aa,311 +*none*: 0x01ab,313 +*none*: 0x01ac,314 +*none*: 0x01ad,315 +*none*: 0x01ae,317 +*none*: 0x01af,318 +*none*: 0x01b0,319 + +[FloppyInitial.mc,v] +DoneOnceOnlyInit: 0x0244,50 +*none*: 0x0245,51 +*none*: 0x0246,52 +clearBank0: 0x0126,54 +*none*: 0x0247,55 +*none*: 0x0248,56 +GFT: 0x0127,60 +*none*: 0x0249,61 +*none*: 0x024a,62 +*none*: 0x024b,64 +*none*: 0x024c,65 +*none*: 0x0112,66 +*none*: 0x024d,68 +*none*: 0x024e,69 +*none*: 0x024f,70 +Remap: 0x0138,73 +*none*: 0x0250,74 +*none*: 0x0116,75 +DoneRemap: 0x0139,80 +*none*: 0x0251,81 +*none*: 0x0252,82 + +[IOP.mc,v] +IOPIdle: 0x0027,86 +*none*: 0x005f,87 +*none*: 0x002c,88 +CReadMem: 0x0021,158 +IOPSubc2: 0x0069,323 +*none*: 0x006a,324 +*none*: 0x006b,327 +*none*: 0x006c,328 +*none*: 0x006d,329 +*none*: 0x006e,332 +*none*: 0x006f,333 +*none*: 0x0070,334 +IOPSub2: 0x0022,339 +*none*: 0x0071,340 +*none*: 0x0072,341 +*none*: 0x0073,344 +*none*: 0x0013,345 +*none*: 0x0011,160 +RFirst: 0x0046,165 +*none*: 0x0047,166 +*none*: 0x0031,167 +RLoByte: 0x0048,172 +IOPSubc1: 0x0020,322 +*none*: 0x0010,102 +*none*: 0x002d,106 +*none*: 0x002e,107 +WLo3: 0x0019,119 +WHiByte: 0x002f,112 +*none*: 0x0030,113 +*none*: 0x003c,114 +WLo3Last: 0x003d,123 +WLo: 0x0032,117 +*none*: 0x0034,118 + diff --git a/D/CP/Source/Main_map.txt b/D/CP/Source/Main_map.txt new file mode 100644 index 0000000..e34ecd0 --- /dev/null +++ b/D/CP/Source/Main_map.txt @@ -0,0 +1,572 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[IOPMain.mc,v] +IOPIdle: 0x0ecc,57 +*none*: 0x014f,58 +IOPNoop: 0x061c,289 +*none*: 0x0c7f,61 +*none*: 0x0404,62 +*none*: 0x0c74,65 +*none*: 0x0ecd,66 +*none*: 0x0ece,67 +*none*: 0x04a5,71 +rTmpLRot8: 0x0226,287 +*none*: 0x0c75,75 +*none*: 0x0c76,79 +*none*: 0x0ecf,80 +CalculateLast: 0x0ed3,117 +*none*: 0x0ed4,118 +TestCarry: 0x0ed5,120 +MapLast: 0x092e,134 +*none*: 0x0ed7,135 +*none*: 0x0ed8,136 +*none*: 0x0ed9,141 +*none*: 0x0eda,142 +*none*: 0x0edb,143 +*none*: 0x0edc,145 +*none*: 0x0edd,146 +*none*: 0x0ede,147 +*none*: 0x0c70,178 +MapVirt3: 0x0890,180 +*none*: 0x0ee5,183 +*none*: 0x0ee6,184 +*none*: 0x0ee7,185 +*none*: 0x0ee8,190 +*none*: 0x0ee9,191 +*none*: 0x061a,192 +WriteBlock: 0x07ca,201 +*none*: 0x0eea,202 +*none*: 0x0942,203 +WriteBlockLoop: 0x0eeb,206 +*none*: 0x0eec,207 +*none*: 0x0699,208 +WritePageCross: 0x069a,232 + +[MultiBankStartMesa.mc,v] +*none*: 0x07fc,83 +*none*: 0x07fd,84 +SetMDS: 0x0aff,89 +*none*: 0x0dea,90 +*none*: 0x0deb,92 +*none*: 0x0dec,98 +*none*: 0x0ded,99 +*none*: 0x0dee,101 +*none*: 0x0def,102 +*none*: 0x0df0,103 +*none*: 0x0df1,118 +*none*: 0x0df2,119 +*none*: 0x0df3,120 +*none*: 0x0df4,122 +*none*: 0x0df5,123 +*none*: 0x0df6,124 +*none*: 0x0df7,127 +*none*: 0x0df8,128 +*none*: 0x0df9,129 +*none*: 0x0dfa,131 +*none*: 0x0904,132 +*none*: 0x0dfb,133 +KeyDone: 0x0905,149 +*none*: 0x0dfc,150 +*none*: 0x0dfd,154 +*none*: 0x0dfe,155 +*none*: 0x0dff,156 +LastChance: 0x0e00,159 +*none*: 0x0e01,160 +*none*: 0x0e02,161 + +[Xfer.mc,v] +XFER: 0x0b22,484 +XferIndirect: 0x042e,517 +*none*: 0x0d8d,518 +*none*: 0x018c,520 +*none*: 0x0701,521 +XReadx: 0x07e1,969 +XReady: 0x09f1,971 +XReadz: 0x0903,972 +*none*: 0x04da,973 +*none*: 0x038c,523 +*none*: 0x0d8e,524 +*none*: 0x0d8f,525 +*none*: 0x0d90,527 +*none*: 0x042c,488 +*none*: 0x0d85,489 +*none*: 0x08e6,491 +ControlTrap: 0x08e7,743 +*none*: 0x02d7,744 +TH1: 0x0dc4,745 +TH: 0x0dc9,756 +*none*: 0x0dca,757 +Tha: 0x0920,758 +*none*: 0x0dcb,760 +THb: 0x0788,761 +THc: 0x0dcc,765 +*none*: 0x0dcd,767 +*none*: 0x0dce,768 +*none*: 0x0dcf,769 +THx: 0x0619,775 +*none*: 0x0dd2,776 +*none*: 0x0dd3,777 +THd: 0x0330,779 +*none*: 0x08a1,780 +TrapGo: 0x007a,782 +*none*: 0x07fa,784 +*none*: 0x0dd4,785 +*none*: 0x0dd5,786 +*none*: 0x0dd6,788 +KFCBa: 0x0d5e,276 +*none*: 0x0d5f,277 +*none*: 0x0d60,279 +*none*: 0x0402,280 +GLe: 0x0ddc,894 +*none*: 0x04fc,896 +*none*: 0x020c,897 +*none*: 0x020d,898 +*none*: 0x0ddd,899 +*none*: 0x0dde,901 +GLc: 0x0443,902 +*none*: 0x04d2,903 +XFMUD: 0x0765,493 +StashPC0: 0x0940,842 +*none*: 0x07c1,847 +*none*: 0x0349,848 +*none*: 0x0334,850 +*none*: 0x0635,852 +StShift: 0x0dd8,855 +*none*: 0x0dd9,856 +StashPCb: 0x04d0,859 +*none*: 0x027a,860 +*none*: 0x0767,495 +*none*: 0x0d86,496 +*none*: 0x020a,498 +*none*: 0x0d87,499 +*none*: 0x0d88,500 +LGC: 0x0dae,656 +LGCx: 0x0daf,657 +*none*: 0x022a,658 +SameG: 0x04fb,701 +*none*: 0x033d,702 +sgOdd: 0x048e,703 +sgEven: 0x048f,704 +sg: 0x0dbe,706 +*none*: 0x0dbf,707 +*none*: 0x07f8,709 +sgY: 0x07f9,708 +UnboundTrap: 0x09fd,748 +*none*: 0x062b,749 +*none*: 0x0dc5,750 +*none*: 0x0dc6,752 +*none*: 0x0dc7,753 +*none*: 0x0dc8,754 +XferProc3: 0x042f,545 +*none*: 0x018d,547 +XNPa: 0x07f0,549 +*none*: 0x04fa,660 +LGCb: 0x07f5,663 +*none*: 0x0db0,664 +*none*: 0x0db1,666 +*none*: 0x04a6,667 +*none*: 0x0db2,668 +XCa: 0x08ec,671 +*none*: 0x0db3,672 +*none*: 0x0adf,674 +xcOdd: 0x0ade,673 +XMapG: 0x0db4,676 +*none*: 0x0db5,677 +*none*: 0x0db6,678 +*none*: 0x0db7,680 +LGCd: 0x07f7,682 +*none*: 0x0db8,683 +*none*: 0x0db9,685 +*none*: 0x0dba,686 +*none*: 0x0dbb,687 +*none*: 0x026e,689 +*none*: 0x0dbc,690 +*none*: 0x0dbd,691 +*none*: 0x09fe,693 +XCe: 0x08ef,694 +XCd: 0x08ee,698 +*none*: 0x01bb,696 +*none*: 0x01ba,695 +XAlloc: 0x02ad,552 +AllocSub: 0x0483,925 +*none*: 0x0347,926 +Alloc1: 0x0217,928 +AllocMUD1: 0x07a5,952 +*none*: 0x07a7,929 +*none*: 0x0de1,930 +*none*: 0x0de2,932 +AV0: 0x04cc,933 +*none*: 0x0368,934 +*none*: 0x0218,936 +*none*: 0x0655,953 +*none*: 0x0657,937 +*none*: 0x0de3,938 +*none*: 0x0de4,940 +*none*: 0x04d8,941 +*none*: 0x003d,554 +*none*: 0x0d92,556 +*none*: 0x0d93,557 +*none*: 0x0d94,558 +*none*: 0x0d95,560 +XPCalI: 0x032e,564 +XPSD: 0x0d9a,579 +*none*: 0x0d9b,581 +*none*: 0x04b6,582 +XferDone: 0x044e,617 +XTail: 0x0da3,619 +*none*: 0x0616,620 + +[CommonSubs.mc,v] +WMapFix: 0x07df,83 +*none*: 0x0160,84 +WMaps: 0x0354,86 +WMapb: 0x02d4,91 +*none*: 0x0acd,247 +*none*: 0x0710,246 +*none*: 0x0ace,248 +*none*: 0x000a,249 + +[Write.mc,v] +W: 0x0211,83 +*none*: 0x0463,84 +WMUD: 0x0461,86 + +[LoadStore.mc,v] +SLa: 0x0669,296 +*none*: 0x0504,185 +LLn: 0x00c6,195 +LLa: 0x00e1,196 +LLb: 0x00e2,197 +@@PLDB: 0x0533,270 +*none*: 0x0240,271 +PLDBa: 0x0359,272 +PLDB2: 0x0b14,275 +PLBx: 0x0125,254 +PLa: 0x04e9,255 + +[Refill.mc,v] +OpTable: 0x0500,126 +*none*: 0x0c78,127 +NoRCross: 0x0248,130 +RefillE: 0x0400,119 +*none*: 0x0228,120 +*none*: 0x016f,281 +*none*: 0x0740,282 +StackErr: 0x0492,299 +DISPNIonly: 0x0c85,261 +UpdatePC: 0x012f,156 +*none*: 0x0c79,157 +*none*: 0x0149,158 +RReMap: 0x0c7a,160 +*none*: 0x0471,161 +JRedo: 0x09ff,189 +*none*: 0x0889,190 +*none*: 0x0476,191 +ECross: 0x03c0,166 +JCross: 0x03cf,187 + +[Misc.mc,v] +@@ESC: 0x05f8,70 +*none*: 0x0b37,71 +*none*: 0x08b7,79 +@@WRMP: 0x0a67,349 +*none*: 0x0b5d,350 +*none*: 0x0b68,351 +*none*: 0x0b6a,353 +*none*: 0x0b6b,354 +@@WRWDC: 0x0a63,333 +WRx: 0x0b53,311 +*none*: 0x08b1,73 +*none*: 0x08b0,72 +@@GMF: 0x08f9,261 +*none*: 0x0161,262 +SMFa: 0x02c0,229 +*none*: 0x0b46,231 +*none*: 0x0b47,232 +*none*: 0x0b48,233 +*none*: 0x0b49,235 +*none*: 0x0b4a,236 +*none*: 0x0b4b,237 +*none*: 0x0b4c,238 +*none*: 0x06c4,240 +*none*: 0x0433,246 +*none*: 0x0b4d,248 +*none*: 0x0b4e,249 +*none*: 0x0b4f,250 +*none*: 0x0392,252 +GMFa: 0x0719,264 +SMFd: 0x0718,253 +*none*: 0x0b50,254 +SMd: 0x0b44,211 +*none*: 0x03ae,218 +ESC0n: 0x08f0,93 +@@SM: 0x08f7,199 +*none*: 0x0b3a,200 +*none*: 0x0b3b,201 +*none*: 0x0b3c,203 +*none*: 0x0b3d,204 +*none*: 0x0b3e,205 +*none*: 0x0b41,207 +*none*: 0x0b42,208 +*none*: 0x0b43,209 +SMc: 0x03a8,212 +@@SMF: 0x08f8,227 +*none*: 0x0b45,228 +*none*: 0x08b8,80 +@@INPUT: 0x0910,271 +*none*: 0x0b51,272 +*none*: 0x0a83,276 + +[Stack.mc,v] +@@DSHIFT: 0x0857,401 +*none*: 0x0cc1,402 +DSa: 0x079c,415 +*none*: 0x02a2,417 +*none*: 0x0cc3,418 +DSc: 0x08a6,407 +*none*: 0x047a,409 +*none*: 0x049a,412 +@@UDCMP: 0x05be,576 +*none*: 0x0ce2,577 +comp: 0x0ce3,578 +*none*: 0x0ce4,580 +CHighNE: 0x08ae,582 +CompG: 0x07b9,585 + +[Jump.mc,v] +@@JGEB: 0x0591,292 +*none*: 0x0adc,293 +*none*: 0x0ae4,303 +jNoOv: 0x03d6,305 +jOv: 0x03d7,306 +jc22: 0x000c,201 +jT: 0x0716,422 +jF: 0x0717,423 +NoJUmp: 0x0033,474 +jPop2Incr2: 0x089d,479 +@@JB: 0x0588,137 +*none*: 0x0180,138 +JPosOdd: 0x0609,428 +JPos: 0x0af6,434 +jnPNoCross: 0x0119,439 +JPtr1Pop0: 0x0872,459 +Jgo: 0x0af9,469 +*none*: 0x06ab,430 +@@JIW: 0x05a1,361 +*none*: 0x00a4,362 +jiCom: 0x0aee,365 +*none*: 0x0aef,367 +*none*: 0x0714,368 +*none*: 0x004d,369 +*none*: 0x01e0,371 +*none*: 0x0af0,372 +*none*: 0x0af1,373 +*none*: 0x0af2,376 +*none*: 0x0af3,377 +*none*: 0x0af4,378 +jiRedo: 0x04fd,380 +*none*: 0x0805,381 +*none*: 0x0350,382 +jibL: 0x0332,384 +ji: 0x012e,388 +jwPos: 0x04c9,148 +*none*: 0x0ad3,151 +jwOdd: 0x03d3,154 +jwCross: 0x0ad4,157 +JPtr0Pop2: 0x087f,467 +jiwL: 0x0336,386 + +[DiskDlionA.mc,v] +GetCSB: 0x0ef8,84 +*none*: 0x070f,85 +*none*: 0x0ef9,86 +*none*: 0x0efa,89 +*none*: 0x0efb,91 +*none*: 0x00ba,93 +NewIOCB: 0x0efc,97 +*none*: 0x0efd,98 +GoodIOCB: 0x0946,100 +*none*: 0x0947,101 +StartIOCB: 0x0efe,105 +*none*: 0x0eff,106 +*none*: 0x0f00,108 +GetCmd: 0x08d0,111 +GetCmdC2: 0x0f01,112 +GetCmdC3: 0x0346,113 +FetArg: 0x074f,118 +SameC2s: 0x0158,123 +SendCtlWd: 0x0159,153 +*none*: 0x0386,156 +SameC3s: 0x0366,124 +Inr: 0x0768,133 +*none*: 0x0f02,135 +*none*: 0x0f03,136 +*none*: 0x0f04,139 +*none*: 0x0f05,140 +IncBr: 0x0f06,141 +DoInc: 0x08d1,145 +FinLdReg: 0x0f1d,247 +LoadPr: 0x076d,162 +LoadRCLpC2: 0x0f08,170 +LoadRCLpC3: 0x06c2,172 +LoadRCLp: 0x0f07,168 +StartRALp: 0x06c3,176 +LoadRALp: 0x0f09,179 +LoadRALpC2: 0x0f0a,182 +LoadRALpC3: 0x06ba,184 +FinLd: 0x06bb,186 +*none*: 0x0f0b,188 +*none*: 0x0f0c,189 +*none*: 0x0f0d,196 +*none*: 0x0f0e,198 +*none*: 0x0f0f,199 +SaveHeadSect: 0x0948,206 +IsHeaderRead: 0x0949,202 +*none*: 0x0f10,210 +*none*: 0x0f11,213 +LdHeadSectC3: 0x03a6,214 +SetUHeadSect: 0x0f12,217 +FixupDone: 0x0f13,220 +*none*: 0x0f14,221 +*none*: 0x0f15,223 +SavePgNum: 0x08d2,228 +IsLabelRead: 0x08d3,225 +FormHdNotOkMsk: 0x0f1b,243 +*none*: 0x0f1c,244 +*none*: 0x015a,255 +*none*: 0x03c6,256 +NewSector: 0x094a,258 +*none*: 0x0f1e,259 +*none*: 0x0f1f,260 +*none*: 0x0f20,262 +*none*: 0x0f21,263 +*none*: 0x0f22,264 +NewHeader: 0x08d4,267 +*none*: 0x0f23,269 +*none*: 0x0a40,270 +HeaderRet: 0x0050,273 +*none*: 0x0f24,275 +*none*: 0x0f25,276 +HeaderWrong: 0x094c,278 +DoLabel: 0x094d,295 +*none*: 0x0f2a,296 +*none*: 0x0f2b,297 +*none*: 0x0f2c,299 +*none*: 0x0f2d,300 +*none*: 0x0861,301 +LabelRet: 0x0051,304 +*none*: 0x0f2e,305 +*none*: 0x0f2f,306 +DoData: 0x094f,318 +LabelQuit: 0x094e,315 +*none*: 0x0f30,320 +NoIncrDatPtr: 0x06f2,322 +IncrDatPtr: 0x06f3,323 +MapDataAddr: 0x0f31,326 +*none*: 0x0f32,329 +GetPhysDatAddr: 0x0406,331 +*none*: 0x0f33,335 +*none*: 0x0f34,336 +*none*: 0x0602,337 +*none*: 0x0f26,280 +*none*: 0x0f27,281 +TstSeenAll: 0x08d7,287 +HeaderQuit: 0x08d6,284 +*none*: 0x0f28,288 +*none*: 0x0f29,289 +NotFound: 0x08d5,292 + +[DiskDlionB.mc,v] +InitRegs: 0x076f,321 +*none*: 0x0f7b,322 +*none*: 0x0f7c,323 +*none*: 0x0f7d,325 +FinishIOCB: 0x076e,233 +*none*: 0x0f61,235 +*none*: 0x01fa,237 +*none*: 0x0f62,240 +*none*: 0x0f63,241 +*none*: 0x021a,242 +*none*: 0x0f64,245 +*none*: 0x0f65,246 +*none*: 0x0f66,247 +*none*: 0x0f67,250 +*none*: 0x0f68,251 +*none*: 0x0f69,252 +ComposeStat: 0x096b,263 +*none*: 0x0f6c,264 +*none*: 0x0f6d,268 +*none*: 0x0f6e,271 +*none*: 0x0f70,272 +*none*: 0x0f71,273 +*none*: 0x0f72,276 +*none*: 0x0f73,277 +MemError: 0x07ef,279 +LastIOCB: 0x0973,286 +ResetFirmwareBusy: 0x096d,291 +GetIntrMask: 0x0f74,300 +*none*: 0x0f75,303 +*none*: 0x0f76,304 +*none*: 0x0f78,305 +*none*: 0x0f79,309 +*none*: 0x0f7a,310 +QuitNow: 0x0cde,313 +TransferField: 0x0f48,87 +*none*: 0x0f49,88 +*none*: 0x0f4a,89 +*none*: 0x0f4b,91 +*none*: 0x0f4c,92 +SetupWrt: 0x0c9f,162 +*none*: 0x0f55,164 +SyncLp2: 0x0f56,169 +*none*: 0x0f57,170 +*none*: 0x0936,168 +FinSync: 0x0937,173 +*none*: 0x0f58,175 +*none*: 0x0f59,177 +SetupHd: 0x0968,181 +MakeSyncAdrMk: 0x0f5a,185 +*none*: 0x0f5b,186 +*none*: 0x0f5c,190 +*none*: 0x0f5d,191 +InitWrtC3: 0x046e,192 +WrtVerLp: 0x0970,197 +WrtVerLpC2: 0x0f5e,198 +WrtVerLpC3: 0x01f2,199 +FinWrtVer: 0x0971,202 +*none*: 0x0f5f,203 +FinWrite: 0x0cbf,207 +FinVerify: 0x0cbe,205 +*none*: 0x06e2,210 +SndWaitC2: 0x0f60,216 +SndWaitC3: 0x02d2,218 +SndWait: 0x07cf,215 +SndFreeze: 0x02d3,222 +SetupRdVer: 0x0c9e,101 +*none*: 0x0f4d,105 +*none*: 0x0f4e,106 +*none*: 0x0f4f,107 +*none*: 0x0f50,113 +*none*: 0x0f51,118 +StartVer: 0x07af,121 +StartRd: 0x07ad,126 +StartRdC1: 0x08c1,130 +ReadLpC2: 0x08dc,137 +ReadLpC3: 0x046a,138 +ReadLp: 0x0f52,136 +FinRead: 0x08dd,147 +FinReadC3: 0x011a,149 +FinReadLp: 0x0f53,152 +RdLastWd: 0x0966,153 +*none*: 0x0f54,154 +FreezeRead: 0x0967,157 + diff --git a/D/CP/Source/MoonCycle_map.txt b/D/CP/Source/MoonCycle_map.txt new file mode 100644 index 0000000..a9ae4a9 --- /dev/null +++ b/D/CP/Source/MoonCycle_map.txt @@ -0,0 +1,89 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonCycle.mc,v] +START: 0x0090,155 +*none*: 0x011f,156 +*none*: 0x0091,157 +*none*: 0x0092,159 +*none*: 0x0093,160 +*none*: 0x0094,161 +*none*: 0x0095,163 +*none*: 0x0096,164 +*none*: 0x0097,165 +*none*: 0x0098,167 +*none*: 0x0099,168 +*none*: 0x009a,169 +*none*: 0x009b,171 +*none*: 0x009c,172 +START1: 0x0036,173 +START2: 0x0037,174 +START3: 0x009d,178 +*none*: 0x009e,179 +*none*: 0x0020,180 +Delay: 0x0052,304 +*none*: 0x00c9,305 +*none*: 0x00ca,306 +DelENRET: 0x0053,308 +*none*: 0x00cb,309 +*none*: 0x0017,310 +Task1: 0x0055,40 +Task2: 0x0067,62 +Task3: 0x0071,84 +Task4: 0x007b,106 +Task5: 0x0086,128 +*none*: 0x0010,182 +*none*: 0x00a0,183 +*none*: 0x0040,184 +CkRZero: 0x00c7,291 +*none*: 0x00c8,292 +ErrorTask1: 0x002d,298 +*none*: 0x002c,293 +ErrorTask2: 0x003b,299 +*none*: 0x003a,294 +ErrorTask3: 0x002f,300 +*none*: 0x002e,295 +ErrorTask4: 0x0051,301 +*none*: 0x0050,296 +ErrorTask5: 0x0043,302 +CkENRET: 0x0042,312 +*none*: 0x00cc,313 +*none*: 0x001e,314 +*none*: 0x0060,187 +*none*: 0x00a1,188 +*none*: 0x0001,189 +*none*: 0x005f,41 +*none*: 0x0056,42 +Task1A: 0x0018,44 +Task1B: 0x0019,48 +*none*: 0x0059,49 +*none*: 0x005a,50 +Task1C: 0x0008,52 +*none*: 0x005b,53 +*none*: 0x005c,54 +Task1D: 0x0009,56 +*none*: 0x005d,57 +*none*: 0x005e,58 +*none*: 0x0057,45 +*none*: 0x0058,46 +*none*: 0x0011,191 +*none*: 0x00a2,192 +*none*: 0x0021,193 +*none*: 0x0061,196 +*none*: 0x00a3,197 +*none*: 0x0002,198 +*none*: 0x0012,200 +*none*: 0x00a4,201 +*none*: 0x0022,202 +*none*: 0x0062,205 +*none*: 0x00a5,206 +*none*: 0x0003,207 + diff --git a/D/CP/Source/MoonEthBTest_map.txt b/D/CP/Source/MoonEthBTest_map.txt new file mode 100644 index 0000000..360169f --- /dev/null +++ b/D/CP/Source/MoonEthBTest_map.txt @@ -0,0 +1,267 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonEthBTest.mc,v] +CkTest: 0x0074,699 +*none*: 0x00bf,700 +*none*: 0x0196,701 +Go: 0x00a9,123 +*none*: 0x009f,124 +*none*: 0x00aa,125 +*none*: 0x00ab,127 +*none*: 0x00ac,128 +*none*: 0x00ad,129 +Go1: 0x0008,131 +Go2: 0x0009,134 +RunGood: 0x0fff,484 +*none*: 0x00ae,135 +*none*: 0x00af,136 +*none*: 0x00b3,138 +*none*: 0x00b4,139 +*none*: 0x00b5,140 +*none*: 0x00b6,144 +*none*: 0x00b7,145 +*none*: 0x00b8,146 +*none*: 0x00b9,150 +*none*: 0x00ba,151 +*none*: 0x00bb,152 +*none*: 0x00bc,154 +*none*: 0x00bd,155 +*none*: 0x00be,156 +Pause: 0x00c8,159 +*none*: 0x00c9,160 +*none*: 0x00ca,161 +Btest0: 0x00cb,164 +*none*: 0x00cc,165 +*none*: 0x00cd,166 +*none*: 0x00ce,168 +*none*: 0x00cf,169 +*none*: 0x00d3,170 +*none*: 0x00d4,172 +*none*: 0x00d5,173 +Badt0: 0x0014,640 +t0Stop: 0x00e0,641 +Btest1: 0x0015,176 +*none*: 0x00d6,178 +*none*: 0x00d7,179 +*none*: 0x00d8,180 +*none*: 0x00d9,182 +*none*: 0x00da,183 +*none*: 0x00db,184 +Btest2: 0x000d,188 +Badt1: 0x000c,643 +t1Stop: 0x00e1,644 +*none*: 0x00dc,189 +*none*: 0x00dd,190 +*none*: 0x00de,192 +*none*: 0x00e3,193 +*none*: 0x00e4,194 +*none*: 0x00e5,196 +*none*: 0x00e6,197 +*none*: 0x00e7,198 +*none*: 0x00e8,200 +*none*: 0x00e9,201 +*none*: 0x0019,202 +Badt2: 0x0018,646 +t2Stop: 0x00e2,647 +*none*: 0x00ea,204 +*none*: 0x00eb,205 +*none*: 0x0020,206 +FifoCk: 0x00ef,225 +*none*: 0x00f3,226 +*none*: 0x00f4,227 +FifoCk1: 0x0025,229 +*none*: 0x00f5,230 +*none*: 0x00f7,231 +*none*: 0x00f8,233 +*none*: 0x00f9,234 +*none*: 0x00fa,235 +FifoRead: 0x0024,238 +FifoRead1: 0x001a,240 +*none*: 0x00fb,241 +FifoErr: 0x0026,246 +*none*: 0x0027,242 +FifoDone: 0x001b,250 +*none*: 0x003c,251 +*none*: 0x0010,208 +*none*: 0x00ec,209 +*none*: 0x0001,210 +*none*: 0x0011,212 +*none*: 0x0012,216 +*none*: 0x0033,247 +*none*: 0x001e,248 +FifoErr0: 0x0100,649 +*none*: 0x0101,650 +*none*: 0x00ed,213 +*none*: 0x0002,214 +*none*: 0x00ee,218 +*none*: 0x0003,219 +Btest4: 0x0013,258 +*none*: 0x00fc,259 +*none*: 0x00fd,260 +*none*: 0x00a0,263 +*none*: 0x00fe,264 +*none*: 0x0104,265 +*none*: 0x0105,268 +*none*: 0x0106,269 +*none*: 0x0107,270 +*none*: 0x0108,272 +*none*: 0x0109,273 +*none*: 0x0040,274 +MemStore: 0x0173,574 +*none*: 0x0174,575 +*none*: 0x0175,576 +*none*: 0x0176,578 +*none*: 0x0177,579 +Pattern: 0x0070,581 +*none*: 0x0071,582 +Sun1: 0x017b,591 +*none*: 0x017c,592 +*none*: 0x017d,593 +Block: 0x017e,595 +*none*: 0x0072,583 +*none*: 0x0073,584 +ADD1: 0x0178,587 +*none*: 0x017f,596 +*none*: 0x0064,597 +MemDone: 0x0065,600 +*none*: 0x0183,601 +*none*: 0x0184,602 +*none*: 0x009b,603 +*none*: 0x00c0,276 +EndWB3: 0x010a,278 +*none*: 0x010b,281 +*none*: 0x010c,283 +*none*: 0x010d,284 +*none*: 0x0021,285 +*none*: 0x00c1,287 +*none*: 0x010e,288 +*none*: 0x0022,289 +*none*: 0x00c2,293 +ClrFin: 0x00c3,302 +*none*: 0x010f,294 +*none*: 0x0110,295 +ClrBuf: 0x0111,297 +*none*: 0x0112,298 +*none*: 0x0023,299 +*none*: 0x0113,303 +*none*: 0x0114,304 +*none*: 0x0115,306 +*none*: 0x0116,309 +*none*: 0x0117,310 +*none*: 0x0118,311 +*none*: 0x0119,316 +*none*: 0x0060,317 +Delay: 0x0044,488 +*none*: 0x015e,490 +*none*: 0x003a,491 +LLDone: 0x003b,495 +TimeOut: 0x0045,525 +*none*: 0x0075,703 +*none*: 0x0197,704 +DoTst: 0x001c,706 +LoopBkS: 0x0198,713 +*none*: 0x0199,714 +*none*: 0x019a,715 +*none*: 0x019b,718 +*none*: 0x019c,719 +*none*: 0x019d,720 +*none*: 0x019e,722 +*none*: 0x019f,723 +*none*: 0x01a0,724 +*none*: 0x01a1,726 +*none*: 0x01a2,727 +*none*: 0x01a3,728 +*none*: 0x01a4,730 +*none*: 0x01a5,731 +*none*: 0x01a6,732 +*none*: 0x01a7,734 +*none*: 0x01a8,737 +LData: 0x000e,738 +LbpLoop: 0x0068,736 +*none*: 0x0069,740 +*none*: 0x01a9,741 +*none*: 0x01aa,742 +*none*: 0x01ab,744 +*none*: 0x01ac,745 +*none*: 0x01ad,746 +Wait: 0x0078,761 +*none*: 0x01b3,762 +*none*: 0x006a,763 +AttenUp: 0x006b,766 +*none*: 0x007a,768 +*none*: 0x01b4,769 +*none*: 0x01b5,770 +ToReTry: 0x006d,774 +*none*: 0x006c,772 +ReTryA: 0x01ae,752 +T7Time: 0x007b,779 +ReadSt: 0x01b6,780 +*none*: 0x01b7,781 +*none*: 0x01b8,783 +*none*: 0x007c,784 +T7Attn1: 0x006f,788 +*none*: 0x006e,785 +TrnBad: 0x01b9,793 +LoopRec: 0x01bc,798 +*none*: 0x01bd,799 +*none*: 0x01be,800 +*none*: 0x01bf,802 +GetWd: 0x0082,805 +RData: 0x00df,806 +AddWd: 0x01c0,804 +*none*: 0x0043,498 +*none*: 0x015f,499 +*none*: 0x0054,501 +*none*: 0x0163,503 +*none*: 0x0046,505 +*none*: 0x0164,506 +CkLoop: 0x0056,508 +*none*: 0x0165,509 +*none*: 0x0166,512 +StatusBad: 0x0048,561 +*none*: 0x0053,562 +*none*: 0x0098,563 +Badt44: 0x00d0,670 +*none*: 0x0049,515 +*none*: 0x0059,517 +*none*: 0x0167,518 +*none*: 0x004b,519 +*none*: 0x0168,520 +*none*: 0x003e,521 +CkLoopL: 0x0030,320 +*none*: 0x011a,321 +*none*: 0x0080,322 +MemCheck: 0x0185,606 +*none*: 0x0186,607 +*none*: 0x0187,608 +MemCheck1: 0x0188,610 +*none*: 0x0189,611 +*none*: 0x018a,612 +*none*: 0x018b,614 +*none*: 0x018c,615 +*none*: 0x018d,616 +*none*: 0x018e,618 +*none*: 0x018f,619 +CheckErr: 0x005c,620 +Check: 0x005d,626 +*none*: 0x0192,627 +*none*: 0x0193,628 +*none*: 0x0066,629 +*none*: 0x0067,632 +*none*: 0x0194,633 +*none*: 0x0195,634 +*none*: 0x009d,635 +WdsGood: 0x0050,326 +*none*: 0x011b,327 +*none*: 0x011c,328 +Btest5: 0x00a1,335 + diff --git a/D/CP/Source/MoonPortIn_map.txt b/D/CP/Source/MoonPortIn_map.txt new file mode 100644 index 0000000..a0be39c --- /dev/null +++ b/D/CP/Source/MoonPortIn_map.txt @@ -0,0 +1,17 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonPortIn.mc,v] +START1: 0x003f,49 +*none*: 0x0004,50 +Wait: 0x0002,52 +*none*: 0x0005,53 + diff --git a/D/CP/Source/MoonPortOut_map.txt b/D/CP/Source/MoonPortOut_map.txt new file mode 100644 index 0000000..8d841a3 --- /dev/null +++ b/D/CP/Source/MoonPortOut_map.txt @@ -0,0 +1,27 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonPortOut.mc,v] +*none*: 0x003f,48 +START: 0x0001,46 +*none*: 0x0004,49 +*none*: 0x0005,50 +Wait: 0x0002,52 +*none*: 0x0006,53 +In: 0x0003,55 +*none*: 0x0007,56 +*none*: 0x0008,57 +Bad: 0x0010,59 +*none*: 0x0011,60 +*none*: 0x0009,61 +*none*: 0x000a,62 +Wait1: 0x0ffe,64 + diff --git a/D/CP/Source/Phase0Protected_map.txt b/D/CP/Source/Phase0Protected_map.txt new file mode 100644 index 0000000..b79b3e5 --- /dev/null +++ b/D/CP/Source/Phase0Protected_map.txt @@ -0,0 +1,42 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[Protected.mc,v] +*none*: 0x001a,77 +Trap: 0x0000,59 +infinite: 0x0001,71 + +[IOPBoot.mc,v] +*none*: 0x0020,62 +*none*: 0x005f,63 +*none*: 0x0023,64 +IOPIdle: 0x000d,70 +IOPIdlex: 0x007f,72 +*none*: 0x0026,75 +CReadMem: 0x0009,84 +ReadWordx: 0x0005,185 +Noop1: 0x0014,208 +*none*: 0x0070,187 +*none*: 0x001b,145 +*none*: 0x002e,149 +*none*: 0x0032,150 +*none*: 0x0013,188 +*none*: 0x0019,87 +*none*: 0x0006,90 +Noop2: 0x000b,207 +*none*: 0x0076,95 +*none*: 0x0015,97 +SendAddress: 0x0075,103 +*none*: 0x0021,105 +*none*: 0x0029,106 +RLoByte: 0x0071,113 +*none*: 0x0028,114 + diff --git a/D/CP/Source/Phase0_map.txt b/D/CP/Source/Phase0_map.txt new file mode 100644 index 0000000..4347c1c --- /dev/null +++ b/D/CP/Source/Phase0_map.txt @@ -0,0 +1,46 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[Phase0.mc,v] +go: 0x0108,97 +*none*: 0x011f,98 +*none*: 0x0146,99 +clear0: 0x0128,102 +*none*: 0x0148,103 +*none*: 0x014a,104 +Trident/SA: 0x013d,115 +*none*: 0x013c,108 +*none*: 0x014e,109 +*none*: 0x0168,110 +Trident: 0x013f,117 +enableIOP: 0x01ae,127 +*none*: 0x01b0,128 +*none*: 0x01c6,129 +*none*: 0x0130,134 +*none*: 0x01c7,135 +*none*: 0x01c8,136 +*none*: 0x016a,116 +SA: 0x013e,118 +pollDevices: 0x018a,120 +*none*: 0x019a,121 +*none*: 0x012b,123 +*none*: 0x012a,124 + +[DiskBootDLion.mc,v] +VerifyLp: 0x0255,97 +*none*: 0x0256,99 +MoreVerify: 0x01d4,101 +FinVerify: 0x01d5,105 +*none*: 0x0160,109 +FindSA1Sect0: 0x013a,110 +FindSA1Sect0Lp: 0x01a8,94 +Wait2: 0x013b,264 + diff --git a/D/CP/Source/SunlightO2_map.txt b/D/CP/Source/SunlightO2_map.txt new file mode 100644 index 0000000..cbc6328 --- /dev/null +++ b/D/CP/Source/SunlightO2_map.txt @@ -0,0 +1,18 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonSunlightO2.mc,v] +Go: 0x00fe,46 +*none*: 0x007f,47 +*none*: 0x0100,48 +*none*: 0x0101,49 +*none*: 0x0016,51 + diff --git a/D/CP/Source/SunlightO4_map.txt b/D/CP/Source/SunlightO4_map.txt new file mode 100644 index 0000000..46c0c7d --- /dev/null +++ b/D/CP/Source/SunlightO4_map.txt @@ -0,0 +1,164 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonSunlightO4.mc,v] +ibTests: 0x0066,61 +*none*: 0x008f,62 +*none*: 0x0067,63 +*none*: 0x0068,64 +*none*: 0x0069,66 +*none*: 0x006a,67 +*none*: 0x006b,68 +IBPtr0Bad: 0x0018,257 +IBPtr0Good: 0x0019,71 +*none*: 0x006c,72 +*none*: 0x006d,73 +*none*: 0x006e,75 +*none*: 0x0070,76 +*none*: 0x0071,77 +IBPtr1Bad: 0x000c,258 +IBPtr1Good: 0x000d,80 +*none*: 0x0072,81 +*none*: 0x0073,82 +*none*: 0x0074,84 +*none*: 0x0075,85 +*none*: 0x0076,86 +*none*: 0x0077,88 +*none*: 0x0078,89 +*none*: 0x0079,90 +IBLeftaBad: 0x0030,259 +IBLeftaGood: 0x0031,92 +IBLeftbBad: 0x0020,261 +IBLeftbGood: 0x0021,93 +*none*: 0x007a,94 +*none*: 0x007b,96 +*none*: 0x007c,97 +IBRaBad: 0x0032,262 +IBRaGood: 0x0033,98 +IBRbBad: 0x0024,264 +IBRbGood: 0x0025,101 +*none*: 0x007d,102 +*none*: 0x007e,103 +IBHighBad: 0x0034,265 +IBHighGood: 0x0035,105 +*none*: 0x007f,106 +*none*: 0x0080,107 +IBLowBad: 0x0026,266 +IBLowGood: 0x0027,110 +*none*: 0x0081,111 +*none*: 0x0082,113 +*none*: 0x0083,114 +*none*: 0x0002,115 +*none*: 0x0084,117 +*none*: 0x0085,118 +*none*: 0x0006,119 +*none*: 0x0086,121 +*none*: 0x0087,122 +*none*: 0x0088,123 +*none*: 0x0089,125 +IBPtr1xBad: 0x0036,267 +IBPtr1xGood: 0x0037,128 +*none*: 0x008a,129 +*none*: 0x008b,130 +*none*: 0x008c,132 +*none*: 0x008d,133 +NETrap: 0x0500,135 +*none*: 0x008e,136 +NETrapBad: 0x0028,268 +NETrapGood: 0x0029,140 +*none*: 0x0090,141 +*none*: 0x0091,142 +*none*: 0x0092,143 +*none*: 0x0093,145 +*none*: 0x0094,146 +*none*: 0x0095,147 +IBaBad: 0x0038,269 +IBaGood: 0x0039,149 +*none*: 0x0096,150 +*none*: 0x0097,151 +IBbBad: 0x002a,270 +IBbGood: 0x002b,153 +*none*: 0x0098,154 +*none*: 0x0099,155 +IBcBad: 0x003a,271 +IBcGood: 0x003b,157 +*none*: 0x009a,158 +*none*: 0x009b,159 +IBdBad: 0x0040,272 +IBdGood: 0x0041,161 +*none*: 0x009c,162 +*none*: 0x009d,163 +ETrap: 0x0400,165 +*none*: 0x009e,166 +ETrapBad: 0x003c,273 +ETrapGood: 0x003d,170 +*none*: 0x009f,171 +*none*: 0x00a0,172 +*none*: 0x00a1,173 +NEMIntTrap: 0x0700,175 +*none*: 0x0600,177 +*none*: 0x00a2,178 +EMTrapG: 0x0043,181 +EMTrapBad: 0x0042,274 +*none*: 0x00a3,182 +*none*: 0x00a4,183 +*none*: 0x00a5,185 +*none*: 0x08af,231 +IBDispaG: 0x00a6,188 +*none*: 0x00a7,189 +*none*: 0x00a8,191 +*none*: 0x00a9,192 +*none*: 0x00aa,193 +*none*: 0x00ab,195 +*none*: 0x00b0,196 +*none*: 0x00b1,197 +*none*: 0x00b2,199 +*none*: 0x00b3,200 +*none*: 0x00b4,201 +IBDispbG: 0x085f,226 +IBDispbG: 0x00b5,204 +*none*: 0x00b6,205 +*none*: 0x00b7,206 +*none*: 0x00b8,208 +*none*: 0x00b9,209 +*none*: 0x08fa,247 +IBDispcG: 0x00ba,212 +*none*: 0x00bb,213 +*none*: 0x00bc,215 +*none*: 0x00bd,216 +*none*: 0x00be,217 +*none*: 0x08f5,242 +MemTest: 0x00c7,288 +*none*: 0x00c8,289 +*none*: 0x00c9,291 +*none*: 0x00ca,292 +*none*: 0x00cb,293 +*none*: 0x00cc,296 +*none*: 0x00cd,297 +*none*: 0x00ce,298 +*none*: 0x00d0,300 +*none*: 0x00d1,301 +*none*: 0x00d2,302 +*none*: 0x00d3,304 +*none*: 0x00d4,305 +*none*: 0x0045,306 +SimMARBad: 0x0044,284 +*none*: 0x00d5,309 +*none*: 0x00d6,310 +*none*: 0x0016,311 +*none*: 0x00d7,313 +*none*: 0x00d8,314 +*none*: 0x00d9,315 +*none*: 0x00da,317 +*none*: 0x00db,318 +MDRCanBad: 0x0052,285 +*none*: 0x0053,319 + diff --git a/D/CP/Source/SunlightO5_map.txt b/D/CP/Source/SunlightO5_map.txt new file mode 100644 index 0000000..a0f2dae --- /dev/null +++ b/D/CP/Source/SunlightO5_map.txt @@ -0,0 +1,183 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[MoonSunlightO5.mc,v] +*none*: 0x002f,78 +TrapTest: 0x0074,77 +*none*: 0x0075,79 +StkUF: 0x0076,82 +*none*: 0x007f,83 +WaitTrapc3: 0x0099,188 +WaitTrapc1: 0x009a,189 +*none*: 0x009b,190 +FastTrapc3: 0x009c,191 +*none*: 0x009d,192 +*none*: 0x005f,45 +*none*: 0x006a,46 +TrapType: 0x0010,49 +*none*: 0x0011,50 +*none*: 0x0012,51 +*none*: 0x0013,52 +NotCSPar: 0x006b,54 +*none*: 0x006c,55 +*none*: 0x006d,58 +*none*: 0x006e,59 +*none*: 0x0024,60 +StkSimUF: 0x0027,85 +*none*: 0x0026,62 +*none*: 0x0001,86 +*none*: 0x001c,63 +StkSim2UF: 0x001d,88 +*none*: 0x0037,89 +*none*: 0x0077,90 +WaitTrapc2: 0x0098,187 +*none*: 0x0028,64 +Stk2UF: 0x0029,92 +*none*: 0x0041,93 +*none*: 0x0078,94 +DidntTrap: 0x00cd,296 +*none*: 0x0030,66 +StkOF: 0x0031,96 +*none*: 0x0039,97 +*none*: 0x002a,67 +StkOFp: 0x002b,99 +*none*: 0x0043,100 +*none*: 0x0079,101 +*none*: 0x0032,68 +StkEReg: 0x0033,104 +*none*: 0x003b,105 +StkEE: 0x0004,107 +*none*: 0x0005,108 +*none*: 0x0006,109 +*none*: 0x0007,110 +Memf: 0x007a,114 +*none*: 0x007b,115 +MWait: 0x0045,117 +*none*: 0x007c,118 +*none*: 0x007d,119 +MemF2: 0x0044,122 +*none*: 0x007e,123 +*none*: 0x0080,125 +*none*: 0x0081,126 +*none*: 0x0082,127 +*none*: 0x0083,129 +*none*: 0x0084,130 +*none*: 0x0085,131 +*none*: 0x006f,70 +*none*: 0x0072,71 +*none*: 0x002c,72 +MemEReg: 0x002d,134 +*none*: 0x003d,135 +MemEE: 0x0014,137 +*none*: 0x0015,138 +MemStatE: 0x0086,142 +*none*: 0x0087,143 +MStatBad0: 0x0009,289 +*none*: 0x000b,144 +*none*: 0x0088,145 +IBTestc1: 0x0021,149 +*none*: 0x0089,150 +*none*: 0x008a,151 +*none*: 0x008b,153 +*none*: 0x008c,154 +*none*: 0x008d,155 +*none*: 0x0034,74 +IBTestc2: 0x0035,158 +*none*: 0x0051,159 +*none*: 0x008e,160 +*none*: 0x008f,162 +*none*: 0x0090,163 +IBEReg: 0x0047,166 +*none*: 0x0053,167 +IBEE: 0x0018,169 +*none*: 0x0019,170 +*none*: 0x001a,171 +*none*: 0x001b,174 +*none*: 0x0091,175 +*none*: 0x0092,176 +*none*: 0x0093,179 +*none*: 0x0094,180 +*none*: 0x0095,181 +*none*: 0x0096,183 +*none*: 0x0097,184 +IBEMemW: 0x0054,302 +CritTime: 0x0055,196 +*none*: 0x009e,199 +*none*: 0x009f,200 +*none*: 0x00a0,201 +*none*: 0x00a1,204 +UZeroBrBad: 0x0048,306 +UNeg: 0x0049,205 +*none*: 0x00a2,206 +*none*: 0x00a3,208 +UNegBrBad: 0x0056,307 +UStkp: 0x0057,209 +*none*: 0x00a4,210 +*none*: 0x00a5,213 +*none*: 0x00a6,214 +*none*: 0x00a7,215 +*none*: 0x00a8,217 +UstkpBad: 0x004a,308 +URot: 0x004b,218 +*none*: 0x00a9,219 +*none*: 0x00aa,222 +*none*: 0x00ab,223 +*none*: 0x00ac,224 +*none*: 0x00ad,226 +*none*: 0x00ae,227 +*none*: 0x0058,309 +UPage: 0x0059,228 +*none*: 0x00af,231 +*none*: 0x00b0,232 +*none*: 0x00b1,233 +*none*: 0x00b2,235 +MemNib: 0x00b3,236 +UByPgCyBad: 0x004c,310 +*none*: 0x004d,237 +*none*: 0x00b4,240 +*none*: 0x00b5,241 +*none*: 0x00b6,242 +*none*: 0x00b7,244 +*none*: 0x00b8,245 +*none*: 0x00b9,246 +*none*: 0x00ba,248 +*none*: 0x00bb,249 +*none*: 0x00bc,250 +MNibByBad: 0x005a,311 +*none*: 0x005b,253 +*none*: 0x00bd,254 +*none*: 0x00be,255 +*none*: 0x00bf,257 +*none*: 0x00c0,258 +*none*: 0x00c1,259 +UFZeroT: 0x004e,261 +*none*: 0x00c2,262 +*none*: 0x00c3,263 +*none*: 0x00c4,265 +*none*: 0x00c5,266 +*none*: 0x005c,267 +rhPage: 0x004f,270 +UFZeroBad: 0x005d,315 +*none*: 0x00c6,271 +*none*: 0x00c7,272 +*none*: 0x00c8,274 +*none*: 0x00c9,275 +*none*: 0x0061,276 +*none*: 0x00ca,279 +RHByArBad: 0x0070,313 +ByCarry: 0x0071,280 +*none*: 0x00cb,281 +*none*: 0x00cc,283 +ByCyBrBad: 0x0062,314 +*none*: 0x0063,284 +NOERROR1: 0x00d3,318 +NOERROR: 0x0fff,319 + diff --git a/D/CP/Source/load_map.txt b/D/CP/Source/load_map.txt new file mode 100644 index 0000000..b3426b8 --- /dev/null +++ b/D/CP/Source/load_map.txt @@ -0,0 +1,13 @@ +BootKernel,BootKernel_map.txt,fd8,fff,08db70caa4585b9a8c7f5ba9215eb34c +Phase0Protected,Phase0Protected_map.txt,000,0ff,c73c5996d9a9ac6ee37a2d92ae7f6c15 +Phase0,Phase0_map.txt,100,fd7,d9d9f75e0f9539fa911488c3bd36e262 +FloppyInitial,FloppyInitial_map.txt,000,fff,0bd42231451c66d8043867fd390580ac +Main,Main_map.txt,000,fd7,7f64459937a5a3104343bc7a0ad5dbd7 +MoonPortIn,MoonPortIn_map.txt,000,fff,00960fada4edd268b6fc49c918d49582 +MoonPortOut,MoonPortOut_map.txt,000,fff,de001dba5266a6fda17d35f88741fa1b +SunlightO2,SunlightO2_map.txt,000,fff,00a2160753bd3a6968dfffba0a8e566b +SunlightO4,SunlightO4_map.txt,000,fff,6c22068f622d7afb5d5ba9a74e2f8312 +SunlightO5,SunlightO5_map.txt,000,fff,6a1971a8953f36e3777f1940e43f2d46 +CPMemTest,CPMemTest_map.txt,000,fff,930c63cb19215d1140e742d86a3ca760 +MoonCycle,MoonCycle_map.txt,000,fff,7936738bb3be4498cdf1f5169f35c1f7 +MoonEthBTest,MoonEthBTest_map.txt,000,fff,990b5b14754ae7785838ace650384671 diff --git a/D/Configuration.cs b/D/Configuration.cs new file mode 100644 index 0000000..701bdbc --- /dev/null +++ b/D/Configuration.cs @@ -0,0 +1,459 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.IOP; +using D.Logging; +using System; +using System.Collections.Specialized; +using System.IO; +using System.Reflection; + +namespace D +{ + public enum PlatformType + { + Windows, + Unix + } + + /// + /// Encapsulates user-configurable settings. To be enhanced. + /// + public static class Configuration + { + static Configuration() + { + // Initialize things to defaults. + MemorySize = 768; + HostID = 0x0000aa012345; + ThrottleSpeed = true; + DisplayScale = 1; + SlowPhosphor = true; + HostPacketInterfaceName = String.Empty; + + TODDateTime = new DateTime(1979, 12, 10); + TODDate = new DateTime(1955, 11, 5); + TODSetMode = TODPowerUpSetMode.HostTimeY2K; + + switch (Environment.OSVersion.Platform) + { + case PlatformID.MacOSX: + case PlatformID.Unix: + Platform = PlatformType.Unix; + break; + + default: + Platform = PlatformType.Windows; + break; + } + + // See if PCap is available. + TestPCap(); + + try + { + ReadConfiguration(); + } + catch + { + Log.Write(LogType.Error, LogComponent.Configuration, + "Unable to load configuration. Assuming default settings."); + } + + // + // Sanity-check settings that need sanity-checking. + // + if (MemorySize > 1024 || + MemorySize == 0 || + (MemorySize % 128) != 0) + { + Log.Write(LogType.Error, LogComponent.Configuration, + "MemorySize configuration parameter is incorrect, defaulting to 768KW."); + + MemorySize = 768; + } + + if (DisplayScale < 1) + { + Log.Write(LogType.Error, LogComponent.Configuration, + "DisplayScale configuration parameter is incorrect, defaulting to 1."); + + DisplayScale = 1; + } + } + + /// + /// What kind of system we're running on. (Not technically configurable.) + /// + public static PlatformType Platform; + + /// + /// System memory size, in KW. + /// + public static uint MemorySize; + + /// + /// The currently loaded image for the hard disk. + /// + public static string HardDriveImage; + + /// + /// The currently loaded image for the floppy drive. + /// + public static string FloppyDriveImage; + + /// + /// The Ethernet host address for this Star. + /// + public static ulong HostID; + + /// + /// The name of the Ethernet adaptor on the emulator host to use for Ethernet emulation + /// + public static string HostPacketInterfaceName; + + /// + /// Whether any packet interfaces are available on the host + /// + public static bool HostRawEthernetInterfacesAvailable; + + /// + /// Whether to cap execution speed at native execution speed or not. + /// + public static bool ThrottleSpeed; + + /// + /// Scale factor to apply to the display. + /// + public static uint DisplayScale; + + /// + /// Whether to apply a fake "slow phosphor" persistence to the emulated display. + /// + public static bool SlowPhosphor; + + /// + /// How to set the TOD clock at power up/reset + /// + public static TODPowerUpSetMode TODSetMode; + + /// + /// The specific date/time to set the TOD clock to if TODSetMode is "SpecificDateAndTime" + /// + public static DateTime TODDateTime; + + /// + /// The specific date to set the TOD clock to if TODSetMode is "SpecificDate" + /// + public static DateTime TODDate; + + /// + /// The components to enable debug logging for. + /// + public static LogComponent LogComponents; + + /// + /// The types of logging to enable. + /// + public static LogType LogTypes; + + /// + /// Reads the current configuration file from the appropriate place. + /// + public static void ReadConfiguration() + { + if (Configuration.Platform == PlatformType.Windows) + // && string.IsNullOrWhiteSpace(StartupOptions.ConfigurationFile)) + { + // + // By default, on Windows we use the app Settings functionality + // to store settings in the registry on a per-user basis. + // If a configuration file is specified, we will use it instead. + // + ReadConfigurationWindows(); + } + else + { + // + // On UNIX platforms we read in a configuration file. + // This is mostly because Mono's support for Properties.Settings + // is broken in inexplicable ways and I'm tired of fighting with it. + // + ReadConfigurationUnix(); + } + } + + /// + /// Commits the current configuration to the app's settings. + /// + public static void WriteConfiguration() + { + if (Configuration.Platform == PlatformType.Windows) + { + WriteConfigurationWindows(); + } + else + { + // + // At the moment the configuration files are read-only + // on UNIX platforms. + // + } + } + + private static void ReadConfigurationWindows() + { + MemorySize = Properties.Settings.Default.MemorySize; + HardDriveImage = Properties.Settings.Default.HardDriveImage; + FloppyDriveImage = Properties.Settings.Default.FloppyDriveImage; + HostID = Properties.Settings.Default.HostAddress; + HostPacketInterfaceName = Properties.Settings.Default.HostPacketInterfaceName; + ThrottleSpeed = Properties.Settings.Default.ThrottleSpeed; + DisplayScale = Properties.Settings.Default.DisplayScale; + SlowPhosphor = Properties.Settings.Default.SlowPhosphor; + TODSetMode = (TODPowerUpSetMode)Properties.Settings.Default.TODSetMode; + TODDateTime = Properties.Settings.Default.TODDateTime; + TODDate = Properties.Settings.Default.TODDate; + } + + private static void WriteConfigurationWindows() + { + Properties.Settings.Default.MemorySize = MemorySize; + Properties.Settings.Default.HardDriveImage = HardDriveImage; + Properties.Settings.Default.FloppyDriveImage = FloppyDriveImage; + Properties.Settings.Default.HostAddress = HostID; + Properties.Settings.Default.HostPacketInterfaceName = HostPacketInterfaceName; + Properties.Settings.Default.ThrottleSpeed = ThrottleSpeed; + Properties.Settings.Default.DisplayScale = DisplayScale; + Properties.Settings.Default.SlowPhosphor = SlowPhosphor; + Properties.Settings.Default.TODSetMode = (int)TODSetMode; + Properties.Settings.Default.TODDateTime = TODDateTime; + Properties.Settings.Default.TODDate = TODDate; + Properties.Settings.Default.Save(); + } + + private static void ReadConfigurationUnix() + { + string configFilePath = null; + + if (false) //!string.IsNullOrWhiteSpace(StartupOptions.ConfigurationFile)) + { + // configFilePath = StartupOptions.ConfigurationFile; + } + else + { + // No config file specified, default. + configFilePath = "Darkstar.cfg"; + } + + // + // Check that the configuration file exists. + // If not, we will warn the user and use default settings. + // + if (!File.Exists(configFilePath)) + { + Console.WriteLine("Configuration file {0} does not exist or cannot be accessed. Using default settings.", configFilePath); + return; + } + + using (StreamReader configStream = new StreamReader(configFilePath)) + { + // + // Config file consists of text lines containing name / value pairs: + // = + // Whitespace is ignored. + // + int lineNumber = 0; + while (!configStream.EndOfStream) + { + lineNumber++; + string line = configStream.ReadLine().Trim(); + + if (string.IsNullOrEmpty(line)) + { + // Empty line, ignore. + continue; + } + + if (line.StartsWith("#")) + { + // Comment to EOL, ignore. + continue; + } + + // Find the '=' separating tokens and ensure there are just two. + string[] tokens = line.Split(new char[] { '=' }, StringSplitOptions.RemoveEmptyEntries); + + if (tokens.Length < 2) + { + Console.WriteLine( + "{0} line {1}: Invalid syntax.", configFilePath, lineNumber); + continue; + } + + string parameter = tokens[0].Trim(); + string value = tokens[1].Trim(); + + // Reflect over the public, static properties in this class and see if the parameter matches one of them + // If not, it's an error, if it is then we attempt to coerce the value to the correct type. + System.Reflection.FieldInfo[] info = typeof(Configuration).GetFields(BindingFlags.Public | BindingFlags.Static); + + bool bMatch = false; + foreach (FieldInfo field in info) + { + // Case-insensitive compare. + if (field.Name.ToLowerInvariant() == parameter.ToLowerInvariant()) + { + bMatch = true; + + // + // Switch on the type of the field and attempt to convert the value to the appropriate type. + // At this time we support only strings and integers. + // + try + { + switch (field.FieldType.Name) + { + case "Int32": + { + int v = Convert.ToInt32(value, 10); + field.SetValue(null, v); + } + break; + + case "UInt16": + { + UInt16 v = Convert.ToUInt16(value, 16); + field.SetValue(null, v); + } + break; + + case "Byte": + { + byte v = Convert.ToByte(value, 16); + field.SetValue(null, v); + } + break; + + case "String": + { + field.SetValue(null, value); + } + break; + + case "Boolean": + { + bool v = bool.Parse(value); + field.SetValue(null, v); + } + break; + + case "UInt64": + { + UInt64 v = Convert.ToUInt64(value, 16); + field.SetValue(null, v); + } + break; + + case "TODPowerUpSetMode": + { + field.SetValue(null, Enum.Parse(typeof(TODPowerUpSetMode), value, true)); + } + break; + + case "DateTime": + { + field.SetValue(null, DateTime.Parse(value)); + } + break; + + case "LogType": + { + field.SetValue(null, Enum.Parse(typeof(LogType), value, true)); + } + break; + + case "LogComponent": + { + field.SetValue(null, Enum.Parse(typeof(LogComponent), value, true)); + } + break; + + case "StringCollection": + { + // value is expected to be a comma-delimited set. + StringCollection sc = new StringCollection(); + string[] strings = value.Split(','); + + foreach (string s in strings) + { + sc.Add(s); + } + + field.SetValue(null, sc); + } + break; + } + } + catch + { + Console.WriteLine( + "{0} line {1}: Value '{2}' is invalid for parameter '{3}'.", configFilePath, lineNumber, value, parameter); + } + } + } + + if (!bMatch) + { + Console.WriteLine( + "{0} line {1}: Unknown configuration parameter '{2}'.", configFilePath, lineNumber, parameter); + } + } + } + } + + private static void TestPCap() + { + // Just try enumerating interfaces, if this fails for any reason we assume + // PCap is not properly installed. + try + { + SharpPcap.CaptureDeviceList devices = SharpPcap.CaptureDeviceList.Instance; + Configuration.HostRawEthernetInterfacesAvailable = true; + } + catch + { + Configuration.HostRawEthernetInterfacesAvailable = false; + } + } + + + } + +} diff --git a/D/Conversion.cs b/D/Conversion.cs new file mode 100644 index 0000000..0019d5d --- /dev/null +++ b/D/Conversion.cs @@ -0,0 +1,49 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D +{ + public static class Conversion + { + /// + /// Conversion from millseconds to nanoseconds + /// + public static readonly ulong MsecToNsec = 1000000; + + /// + /// Conversion from nanoseconds to milliseconds + /// + public static readonly double NsecToMsec = 0.000001; + + /// + /// Conversion from microseconds to nanoseconds + /// + public static readonly ulong UsecToNsec = 1000; + } +} diff --git a/D/Dandelion.ico b/D/Dandelion.ico new file mode 100644 index 0000000..6fea64e Binary files /dev/null and b/D/Dandelion.ico differ diff --git a/D/Darkstar.cfg b/D/Darkstar.cfg new file mode 100644 index 0000000..c3b1bd5 --- /dev/null +++ b/D/Darkstar.cfg @@ -0,0 +1,34 @@ +# darkstar.cfg: +# +# This file contains configuration parameters for Darkstar. +# All integers are specified in hexadecimal. +# + +# System configuration +MemorySize = 0x400 +HostID = 0x0000aa012345 + +# Disk options +# FloppyDriveImage = +# HardDriveImage = + +# Ethernet configuration +# HostPacketInterfaceName = + +# Display options +DisplayScale = 1 +SlowPhosphor = true + +# TOD (Time Of Day) Clock options +# TODSetMode specifies how to set the TOD clock at reset +# (see readme.txt). Possible values are: +# HostTimeY2K +# HostTime +# SpecificDateAndTime +# SpecificDate +# NoChange +# +TODSetMode = NoChange +# TODDate = 1990/11/09 +# TODDateTime = 1990/11/09 12:00:00 + diff --git a/D/Darkstar.csproj b/D/Darkstar.csproj new file mode 100644 index 0000000..1140bf5 --- /dev/null +++ b/D/Darkstar.csproj @@ -0,0 +1,306 @@ + + + + + Debug + AnyCPU + {0590465E-1D91-4591-946E-EE3F7D82834B} + Exe + D + Darkstar + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + app.manifest + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + ..\packages\Sharp_Pcap.4.2.0\lib\PacketDotNet.dll + + + ..\packages\SDL2-CS.dll.2.0.0.0\lib\net20\SDL2-CS.dll + + + ..\packages\Sharp_Pcap.4.2.0\lib\SharpPcap.dll + + + + + + + + + + + + + + + + + + + + + + + Form + + + CPDebugger.cs + + + Form + + + DebuggerMain.cs + + + Form + + + IOPDebugger.cs + + + Form + + + LoadMapDialog.cs + + + + Component + + + Component + + + + + + + True + True + Settings.settings + + + Form + + + AboutBox.cs + + + Form + + + ConfigurationDialog.cs + + + Form + + + Form + + + DWindow.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + PreserveNewest + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + CPDebugger.cs + + + DebuggerMain.cs + + + IOPDebugger.cs + + + LoadMapDialog.cs + + + AboutBox.cs + + + ConfigurationDialog.cs + + + DWindow.cs + + + + \ No newline at end of file diff --git a/D/Debugger/BreakpointManager.cs b/D/Debugger/BreakpointManager.cs new file mode 100644 index 0000000..5efdb5e --- /dev/null +++ b/D/Debugger/BreakpointManager.cs @@ -0,0 +1,178 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; + +namespace D.Debugger +{ + [Flags] + public enum BreakpointType + { + None = 0, + Execution = 1, + Read = 2, + Write = 4, + } + + public enum BreakpointProcessor + { + IOP = 0, + CP, + Mesa, + } + + public struct BreakpointEntry + { + public BreakpointEntry(BreakpointProcessor processor, BreakpointType type, int address) + { + Processor = processor; + Type = type; + Address = address; + } + + public BreakpointProcessor Processor; + public BreakpointType Type; + public int Address; + } + + public static class BreakpointManager + { + static BreakpointManager() + { + // We use a flat array to make lookup as + // cheap as possible across the various address spaces. + _iopBreakpoints = new BreakpointType[0x10000]; + _cpBreakpoints = new BreakpointType[0x1000]; + _mesaBreakpoints = new BreakpointType[0x200000]; // 2mb (1mw). + _enableBreakpoints = true; + } + + public static bool BreakpointsEnabled + { + get { return _enableBreakpoints; } + set { _enableBreakpoints = value; } + } + + public static void SetBreakpoint(BreakpointEntry entry) + { + BreakpointType[] _bkpts = GetBreakpointsForProcessor(entry.Processor); + + if (entry.Type == BreakpointType.None) + { + _bkpts[entry.Address] = BreakpointType.None; + } + else + { + _bkpts[entry.Address] |= entry.Type; + } + } + + public static BreakpointType GetBreakpoint(BreakpointProcessor processor, ushort address) + { + BreakpointType[] _bkpts = GetBreakpointsForProcessor(processor); + return _bkpts[address]; + } + + public static bool TestBreakpoint(BreakpointEntry entry) + { + if (!_enableBreakpoints) + { + return false; + } + + BreakpointType[] _bkpts = GetBreakpointsForProcessor(entry.Processor); + return (_bkpts[entry.Address] & entry.Type) != 0; + } + + public static bool TestBreakpoint(BreakpointProcessor processor, BreakpointType type, int address) + { + if (!_enableBreakpoints) + { + return false; + } + + BreakpointType[] _bkpts = GetBreakpointsForProcessor(processor); + return (_bkpts[address] & type) != 0; + } + + public static List EnumerateBreakpoints() + { + List breakpoints = new List(); + + for (ushort i = 0; i < _iopBreakpoints.Length; i++) + { + if (_iopBreakpoints[i] != BreakpointType.None) + { + breakpoints.Add(new BreakpointEntry(BreakpointProcessor.IOP, _iopBreakpoints[i], i)); + } + } + + for (ushort i = 0; i < _cpBreakpoints.Length; i++) + { + if (_cpBreakpoints[i] != BreakpointType.None) + { + breakpoints.Add(new BreakpointEntry(BreakpointProcessor.CP, _cpBreakpoints[i], i)); + } + } + + for (ushort i = 0; i < _mesaBreakpoints.Length; i++) + { + if (_mesaBreakpoints[i] != BreakpointType.None) + { + breakpoints.Add(new BreakpointEntry(BreakpointProcessor.Mesa, _mesaBreakpoints[i], i)); + } + } + + return breakpoints; + } + + private static BreakpointType[] GetBreakpointsForProcessor(BreakpointProcessor processor) + { + switch (processor) + { + case BreakpointProcessor.CP: + return _cpBreakpoints; + + case BreakpointProcessor.IOP: + return _iopBreakpoints; + + case BreakpointProcessor.Mesa: + return _mesaBreakpoints; + } + + return null; + } + + private static BreakpointType[] _iopBreakpoints; + private static BreakpointType[] _cpBreakpoints; + private static BreakpointType[] _mesaBreakpoints; + private static bool _enableBreakpoints; + } +} diff --git a/D/Debugger/CPDebugger.Designer.cs b/D/Debugger/CPDebugger.Designer.cs new file mode 100644 index 0000000..8e823e6 --- /dev/null +++ b/D/Debugger/CPDebugger.Designer.cs @@ -0,0 +1,457 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.Debugger +{ + partial class CPDebugger + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle(); + this.IOPSourceBox = new System.Windows.Forms.GroupBox(); + this.Disassembly = new D.Debugger.MicrocodeDisplay(); + this.SourceFiles = new System.Windows.Forms.ListBox(); + this.SourceDisplay = new D.Debugger.SourceDisplay(); + this.dataGridViewCheckBoxColumn3 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn5 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn6 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn1 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn2 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn3 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn4 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn4 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn7 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn8 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn5 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn9 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn10 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn6 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn11 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn12 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn7 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn13 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn14 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn8 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn15 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn16 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewCheckBoxColumn9 = new System.Windows.Forms.DataGridViewCheckBoxColumn(); + this.dataGridViewTextBoxColumn17 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn18 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.IOPSourceBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.Disassembly)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.SourceDisplay)).BeginInit(); + this.SuspendLayout(); + // + // IOPSourceBox + // + this.IOPSourceBox.Controls.Add(this.Disassembly); + this.IOPSourceBox.Controls.Add(this.SourceFiles); + this.IOPSourceBox.Controls.Add(this.SourceDisplay); + this.IOPSourceBox.Location = new System.Drawing.Point(10, 5); + this.IOPSourceBox.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.IOPSourceBox.Name = "IOPSourceBox"; + this.IOPSourceBox.Padding = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.IOPSourceBox.Size = new System.Drawing.Size(1256, 1129); + this.IOPSourceBox.TabIndex = 2; + this.IOPSourceBox.TabStop = false; + this.IOPSourceBox.Text = "Microcode Source"; + // + // Disassembly + // + this.Disassembly.AllowUserToAddRows = false; + this.Disassembly.AllowUserToDeleteRows = false; + this.Disassembly.AllowUserToResizeColumns = false; + this.Disassembly.AllowUserToResizeRows = false; + dataGridViewCellStyle1.BackColor = System.Drawing.Color.Silver; + this.Disassembly.AlternatingRowsDefaultCellStyle = dataGridViewCellStyle1; + this.Disassembly.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.Disassembly.DefaultCellStyle = dataGridViewCellStyle2; + this.Disassembly.Location = new System.Drawing.Point(230, 712); + this.Disassembly.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.Disassembly.Name = "Disassembly"; + this.Disassembly.RowHeadersVisible = false; + this.Disassembly.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.CellSelect; + this.Disassembly.Size = new System.Drawing.Size(1017, 403); + this.Disassembly.TabIndex = 2; + this.Disassembly.VirtualMode = true; + this.Disassembly.SelectionChanged += new System.EventHandler(this.OnDisassemblySelectionChanged); + // + // SourceFiles + // + this.SourceFiles.FormattingEnabled = true; + this.SourceFiles.ItemHeight = 20; + this.SourceFiles.Location = new System.Drawing.Point(10, 31); + this.SourceFiles.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.SourceFiles.Name = "SourceFiles"; + this.SourceFiles.SelectionMode = System.Windows.Forms.SelectionMode.MultiExtended; + this.SourceFiles.Size = new System.Drawing.Size(206, 1084); + this.SourceFiles.TabIndex = 1; + this.SourceFiles.Click += new System.EventHandler(this.OnSourceFilesClicked); + this.SourceFiles.DoubleClick += new System.EventHandler(this.OnSourceFilesDoubleClicked); + // + // SourceDisplay + // + this.SourceDisplay.AllowUserToAddRows = false; + this.SourceDisplay.AllowUserToDeleteRows = false; + this.SourceDisplay.AllowUserToResizeColumns = false; + this.SourceDisplay.AllowUserToResizeRows = false; + this.SourceDisplay.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.SourceDisplay.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleVertical; + this.SourceDisplay.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle3.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle3.NullValue = null; + dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.SourceDisplay.DefaultCellStyle = dataGridViewCellStyle3; + this.SourceDisplay.Location = new System.Drawing.Point(228, 29); + this.SourceDisplay.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.SourceDisplay.Name = "SourceDisplay"; + this.SourceDisplay.RowHeadersVisible = false; + this.SourceDisplay.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; + this.SourceDisplay.RowTemplate.Height = 18; + this.SourceDisplay.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.SourceDisplay.Size = new System.Drawing.Size(1018, 672); + this.SourceDisplay.TabIndex = 0; + this.SourceDisplay.VirtualMode = true; + this.SourceDisplay.SelectionChanged += new System.EventHandler(this.OnSourceDisplaySelectionChanged); + this.SourceDisplay.MouseClick += new System.Windows.Forms.MouseEventHandler(this.OnSourceDisplayMouseClick); + // + // dataGridViewCheckBoxColumn3 + // + this.dataGridViewCheckBoxColumn3.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn3.HeaderText = "B"; + this.dataGridViewCheckBoxColumn3.Name = "dataGridViewCheckBoxColumn3"; + this.dataGridViewCheckBoxColumn3.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn5 + // + this.dataGridViewTextBoxColumn5.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn5.HeaderText = "Address"; + this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5"; + this.dataGridViewTextBoxColumn5.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn5.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn6 + // + this.dataGridViewTextBoxColumn6.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn6.HeaderText = "Source"; + this.dataGridViewTextBoxColumn6.Name = "dataGridViewTextBoxColumn6"; + this.dataGridViewTextBoxColumn6.ReadOnly = true; + this.dataGridViewTextBoxColumn6.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn6.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn1 + // + this.dataGridViewCheckBoxColumn1.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn1.HeaderText = "B"; + this.dataGridViewCheckBoxColumn1.Name = "dataGridViewCheckBoxColumn1"; + this.dataGridViewCheckBoxColumn1.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn1 + // + this.dataGridViewTextBoxColumn1.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn1.HeaderText = "Address"; + this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1"; + this.dataGridViewTextBoxColumn1.ReadOnly = true; + this.dataGridViewTextBoxColumn1.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn1.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn2 + // + this.dataGridViewTextBoxColumn2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn2.HeaderText = "Disassembly"; + this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; + this.dataGridViewTextBoxColumn2.ReadOnly = true; + this.dataGridViewTextBoxColumn2.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn2.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn2 + // + this.dataGridViewCheckBoxColumn2.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn2.HeaderText = "B"; + this.dataGridViewCheckBoxColumn2.Name = "dataGridViewCheckBoxColumn2"; + this.dataGridViewCheckBoxColumn2.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn3 + // + this.dataGridViewTextBoxColumn3.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn3.HeaderText = "Address"; + this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3"; + this.dataGridViewTextBoxColumn3.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn3.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn4 + // + this.dataGridViewTextBoxColumn4.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn4.HeaderText = "Source"; + this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4"; + this.dataGridViewTextBoxColumn4.ReadOnly = true; + this.dataGridViewTextBoxColumn4.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn4.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn4 + // + this.dataGridViewCheckBoxColumn4.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn4.HeaderText = "B"; + this.dataGridViewCheckBoxColumn4.Name = "dataGridViewCheckBoxColumn4"; + this.dataGridViewCheckBoxColumn4.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn7 + // + this.dataGridViewTextBoxColumn7.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn7.HeaderText = "Address"; + this.dataGridViewTextBoxColumn7.Name = "dataGridViewTextBoxColumn7"; + this.dataGridViewTextBoxColumn7.ReadOnly = true; + this.dataGridViewTextBoxColumn7.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn7.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn8 + // + this.dataGridViewTextBoxColumn8.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn8.HeaderText = "Disassembly"; + this.dataGridViewTextBoxColumn8.Name = "dataGridViewTextBoxColumn8"; + this.dataGridViewTextBoxColumn8.ReadOnly = true; + this.dataGridViewTextBoxColumn8.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn8.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn5 + // + this.dataGridViewCheckBoxColumn5.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn5.HeaderText = "B"; + this.dataGridViewCheckBoxColumn5.Name = "dataGridViewCheckBoxColumn5"; + this.dataGridViewCheckBoxColumn5.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn9 + // + this.dataGridViewTextBoxColumn9.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn9.HeaderText = "Address"; + this.dataGridViewTextBoxColumn9.Name = "dataGridViewTextBoxColumn9"; + this.dataGridViewTextBoxColumn9.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn9.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn10 + // + this.dataGridViewTextBoxColumn10.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn10.HeaderText = "Source"; + this.dataGridViewTextBoxColumn10.Name = "dataGridViewTextBoxColumn10"; + this.dataGridViewTextBoxColumn10.ReadOnly = true; + this.dataGridViewTextBoxColumn10.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn10.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn6 + // + this.dataGridViewCheckBoxColumn6.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn6.HeaderText = "B"; + this.dataGridViewCheckBoxColumn6.Name = "dataGridViewCheckBoxColumn6"; + this.dataGridViewCheckBoxColumn6.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn11 + // + this.dataGridViewTextBoxColumn11.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn11.HeaderText = "Address"; + this.dataGridViewTextBoxColumn11.Name = "dataGridViewTextBoxColumn11"; + this.dataGridViewTextBoxColumn11.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn11.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn12 + // + this.dataGridViewTextBoxColumn12.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn12.HeaderText = "Disassembly"; + this.dataGridViewTextBoxColumn12.Name = "dataGridViewTextBoxColumn12"; + this.dataGridViewTextBoxColumn12.ReadOnly = true; + this.dataGridViewTextBoxColumn12.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn12.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn7 + // + this.dataGridViewCheckBoxColumn7.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn7.HeaderText = "B"; + this.dataGridViewCheckBoxColumn7.Name = "dataGridViewCheckBoxColumn7"; + this.dataGridViewCheckBoxColumn7.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn13 + // + this.dataGridViewTextBoxColumn13.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn13.HeaderText = "Address"; + this.dataGridViewTextBoxColumn13.Name = "dataGridViewTextBoxColumn13"; + this.dataGridViewTextBoxColumn13.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn13.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn14 + // + this.dataGridViewTextBoxColumn14.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn14.HeaderText = "Source"; + this.dataGridViewTextBoxColumn14.Name = "dataGridViewTextBoxColumn14"; + this.dataGridViewTextBoxColumn14.ReadOnly = true; + this.dataGridViewTextBoxColumn14.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn14.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn8 + // + this.dataGridViewCheckBoxColumn8.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn8.HeaderText = "B"; + this.dataGridViewCheckBoxColumn8.Name = "dataGridViewCheckBoxColumn8"; + this.dataGridViewCheckBoxColumn8.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn15 + // + this.dataGridViewTextBoxColumn15.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn15.HeaderText = "Address"; + this.dataGridViewTextBoxColumn15.Name = "dataGridViewTextBoxColumn15"; + this.dataGridViewTextBoxColumn15.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn15.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn16 + // + this.dataGridViewTextBoxColumn16.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn16.HeaderText = "Disassembly"; + this.dataGridViewTextBoxColumn16.Name = "dataGridViewTextBoxColumn16"; + this.dataGridViewTextBoxColumn16.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn16.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewCheckBoxColumn9 + // + this.dataGridViewCheckBoxColumn9.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewCheckBoxColumn9.HeaderText = "B"; + this.dataGridViewCheckBoxColumn9.Name = "dataGridViewCheckBoxColumn9"; + this.dataGridViewCheckBoxColumn9.Resizable = System.Windows.Forms.DataGridViewTriState.False; + // + // dataGridViewTextBoxColumn17 + // + this.dataGridViewTextBoxColumn17.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.ColumnHeader; + this.dataGridViewTextBoxColumn17.HeaderText = "Address"; + this.dataGridViewTextBoxColumn17.Name = "dataGridViewTextBoxColumn17"; + this.dataGridViewTextBoxColumn17.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn17.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // dataGridViewTextBoxColumn18 + // + this.dataGridViewTextBoxColumn18.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill; + this.dataGridViewTextBoxColumn18.HeaderText = "Source"; + this.dataGridViewTextBoxColumn18.Name = "dataGridViewTextBoxColumn18"; + this.dataGridViewTextBoxColumn18.ReadOnly = true; + this.dataGridViewTextBoxColumn18.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewTextBoxColumn18.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable; + // + // CPDebugger + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1274, 1138); + this.ControlBox = false; + this.Controls.Add(this.IOPSourceBox); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.KeyPreview = true; + this.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CPDebugger"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.Text = "CP Debugger"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.CPDebugger_FormClosed); + this.IOPSourceBox.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.Disassembly)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.SourceDisplay)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.GroupBox IOPSourceBox; + private SourceDisplay SourceDisplay; + private System.Windows.Forms.ListBox SourceFiles; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn3; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn5; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn6; + private MicrocodeDisplay Disassembly; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn1; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn2; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn3; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn4; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn4; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn7; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn5; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn6; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn11; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn12; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn7; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn13; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn14; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn8; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn15; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn16; + private System.Windows.Forms.DataGridViewCheckBoxColumn dataGridViewCheckBoxColumn9; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn17; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn18; + } +} \ No newline at end of file diff --git a/D/Debugger/CPDebugger.cs b/D/Debugger/CPDebugger.cs new file mode 100644 index 0000000..1b81a0b --- /dev/null +++ b/D/Debugger/CPDebugger.cs @@ -0,0 +1,304 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +namespace D.Debugger +{ + public partial class CPDebugger : Form + { + public CPDebugger(DSystem system) + { + InitializeComponent(); + + _system = system; + + SourceDisplay.SetSourceRoot(Path.Combine("CP", "Source")); + + _loadMap = new MicrocodeLoadMap(); + _loadMap.Load(Path.Combine("CP", "Source", "load_map.txt")); + + _sourceMaps = new List(); + + Disassembly.AttachCP(system.CP); + + PopulateSourceList(); + } + + public void DisplayCurrentCode() + { + RefreshSourceMaps(); + + // + // Select active files in the source file list + // + SourceFiles.ClearSelected(); + + foreach(SourceMap s in _sourceMaps) + { + foreach(string fileName in s.GetSourceFiles()) + { + for (int i = 0; i < SourceFiles.Items.Count; i++) + { + if (fileName == (string)SourceFiles.Items[i]) + { + SourceFiles.SetSelected(i, true); + } + } + } + } + + // + // Find the source line that matches the current TPC, if any. + // + int tpc = _system.CP.TPC[(int)_system.CP.CurrentTask]; + + foreach (SourceMap s in _sourceMaps) + { + SourceEntry entry = s.GetExactSourceForAddress((ushort)tpc); + + if (entry != null) + { + // WE GOT ONE!!! + SourceDisplay.AttachMap(s); + SourceDisplay.SelectSourceEntry(entry, false, false /* cp */); + + // Select the source file in the source list + for (int i = 0; i < SourceFiles.Items.Count; i++) + { + if (entry.SourcePath == (string)SourceFiles.Items[i]) + { + SourceFiles.SetSelected(i, true); + } + } + + break; + } + } + + // + // Highlight the line in the disassembly as well. + // + Disassembly.SelectAddress(tpc); + } + + public SourceEntry GetSymbolForAddress(int address) + { + RefreshSourceMaps(); + + SourceEntry entry = null; + + foreach (SourceMap s in _sourceMaps) + { + entry = s.GetSourceForAddress((ushort)address); + + if (entry != null) + { + break; + } + } + + return entry; + } + + public void Save() + { + SaveSourceMaps(); + } + + private void PopulateSourceList() + { + SourceFiles.Items.Clear(); + + // TODO: factor out path generation + IEnumerable files = Directory.EnumerateFiles(Path.Combine("CP", "Source"), "*.mc,v", SearchOption.TopDirectoryOnly); + + foreach(string file in files) + { + SourceFiles.Items.Add(Path.GetFileName(file)); + } + } + + private void OnSourceFilesClicked(object sender, EventArgs e) + { + + } + + private void OnSourceDisplayMouseClick(object sender, MouseEventArgs e) + { + // + // On right click when unmapped file is displayed, we will bring up the map dialog. + // + if (e.Button == MouseButtons.Right) + { + if (SourceDisplay.ReadOnly) + { + LoadMapDialog mapDialog = new LoadMapDialog(SourceDisplay.CurrentSourceFile, _loadMap, _sourceMaps, _system.CP.MicrocodeRam); + DialogResult res = mapDialog.ShowDialog(this); + + if (res == DialogResult.OK) + { + RefreshSourceMaps(); + SourceDisplay.ReadOnly = false; + SourceDisplay.AttachMap(mapDialog.SelectedMap); + } + } + } + } + + private void OnSourceFilesDoubleClicked(object sender, EventArgs e) + { + string selectedFile = (string)SourceFiles.SelectedItem; + + // + // If none of the source maps contain this file, we will display the source in read-only mode + // (no annotations allowed). + // + RefreshSourceMaps(); + + bool mapped = false; + + foreach (SourceMap m in _sourceMaps) + { + if (m.GetSourceFiles().Contains(selectedFile)) + { + SourceDisplay.AttachMap(m); + mapped = true; + break; + } + } + + SourceDisplay.SelectSourceEntry(new SourceEntry((string)SourceFiles.SelectedItem, new string[] { }, 0, 1), !mapped, false /* cp */); + } + + private void CPDebugger_FormClosed(object sender, FormClosedEventArgs e) + { + _loadMap.Save(Path.Combine("CP", "Source", "load_map.txt")); // TODO: move to constant + SaveSourceMaps(); + } + + private void RefreshSourceMaps() + { + List mapEntries = _loadMap.FindEntries(_system.CP.MicrocodeRam); + + // + // See if any entries have changed, if so reload source maps. + // + bool changed = _mapEntries == null || mapEntries.Count != _mapEntries.Count; + + if (!changed) + { + for (int i = 0; i < mapEntries.Count; i++) + { + // + // This assumes that ordering remains constant, which works at the moment. + // + if (mapEntries[i].Name != _mapEntries[i].Name) + { + changed = true; + break; + } + } + } + + if (changed) + { + // + // Commit any existing source maps back to disk. + // + SaveSourceMaps(); + + // Build a new set of source maps. + _sourceMaps.Clear(); + _mapEntries = mapEntries; + + foreach (LoadMapEntry e in _mapEntries) + { + SourceMap newMap = new SourceMap( + e.Name, + Path.Combine("CP", "Source", e.MapName), + Path.Combine("CP", "Source")); + + _sourceMaps.Add(newMap); + } + } + } + + private void SaveSourceMaps() + { + foreach (SourceMap m in _sourceMaps) + { + m.Save(); + } + } + + private MicrocodeLoadMap _loadMap; + private List _mapEntries; + private List _sourceMaps; + + private DSystem _system; + + private void OnSourceDisplaySelectionChanged(object sender, EventArgs e) + { + // + // Sync the disassembly display with the selected source address, if possible. + // + int address = SourceDisplay.SelectedAddress; + + if (address != -1 && Disassembly.SelectedAddress != address) + { + Disassembly.SelectAddress(address); + } + } + + private void OnDisassemblySelectionChanged(object sender, EventArgs e) + { + // + // Sync the source display with the selected disassembly address, if possible. + // + int address = Disassembly.SelectedAddress; + + // + // TODO: we should search through all source maps, not just the current one. + // + if (SourceDisplay.SourceMap != null && address != -1 && SourceDisplay.SelectedAddress != address) + { + SourceEntry entry = SourceDisplay.SourceMap.GetSourceForAddress((ushort)address); + + if (entry != null && !string.IsNullOrWhiteSpace(entry.SourcePath)) + { + SourceDisplay.SelectSourceEntry(entry, false, false /* cp */); + } + } + } + } +} diff --git a/D/Debugger/CPDebugger.resx b/D/Debugger/CPDebugger.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/Debugger/CPDebugger.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/Debugger/DebuggerMain.Designer.cs b/D/Debugger/DebuggerMain.Designer.cs new file mode 100644 index 0000000..56d675f --- /dev/null +++ b/D/Debugger/DebuggerMain.Designer.cs @@ -0,0 +1,109 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.Debugger +{ + partial class DebuggerMain + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.DebugOutput = new System.Windows.Forms.TextBox(); + this.DebuggerInput = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // DebugOutput + // + this.DebugOutput.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.DebugOutput.Location = new System.Drawing.Point(3, 12); + this.DebugOutput.Multiline = true; + this.DebugOutput.Name = "DebugOutput"; + this.DebugOutput.ReadOnly = true; + this.DebugOutput.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.DebugOutput.Size = new System.Drawing.Size(837, 605); + this.DebugOutput.TabIndex = 3; + // + // DebuggerInput + // + this.DebuggerInput.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.DebuggerInput.Location = new System.Drawing.Point(3, 622); + this.DebuggerInput.Multiline = true; + this.DebuggerInput.Name = "DebuggerInput"; + this.DebuggerInput.Size = new System.Drawing.Size(837, 20); + this.DebuggerInput.TabIndex = 2; + this.DebuggerInput.PreviewKeyDown += new System.Windows.Forms.PreviewKeyDownEventHandler(this.DebuggerInput_PreviewKeyDown); + // + // DebuggerMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(845, 647); + this.Controls.Add(this.DebugOutput); + this.Controls.Add(this.DebuggerInput); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.KeyPreview = true; + this.MaximizeBox = false; + this.Name = "DebuggerMain"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.Text = "Debugger Console"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.OnFormClosing); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox DebugOutput; + private System.Windows.Forms.TextBox DebuggerInput; + } +} \ No newline at end of file diff --git a/D/Debugger/DebuggerMain.cs b/D/Debugger/DebuggerMain.cs new file mode 100644 index 0000000..eda5ff6 --- /dev/null +++ b/D/Debugger/DebuggerMain.cs @@ -0,0 +1,1336 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using D.IO; +using D.IOP; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace D.Debugger +{ + public enum DebuggerReason + { + UserInvoked, + Error, + } + + public partial class DebuggerMain : Form + { + public DebuggerMain(DSystem system, DebuggerReason reason, string message) + { + InitializeComponent(); + + _reason = reason; + _entryMessage = message; + _system = system; + } + + protected override void OnLoad(EventArgs e) + { + // + // Pop up some debugger source windows. + // + _iopDebugger = new IOPDebugger(_system); + _iopDebugger.Show(); + + _cpDebugger = new CPDebugger(_system); + _cpDebugger.Show(); + + this.BringToFront(); + + switch(_reason) + { + case DebuggerReason.UserInvoked: + WriteLine(_entryMessage); + break; + + case DebuggerReason.Error: + WriteLine(_entryMessage); + PrintIOPStatus(); + PrintCPStatus(); + PrintMesaStatus(); + break; + } + + base.OnLoad(e); + } + + protected override void OnKeyDown(KeyEventArgs e) + { + // We just intercept Ctrl+C here + if (e.Control && e.KeyCode == Keys.C && _system.IsExecuting) + { + WriteLine("*user break*"); + StopExecution(); + DisplayCurrentCode(); + e.Handled = true; + } + else + { + base.OnKeyDown(e); + } + } + + private void DebuggerInput_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e) + { + if (e.KeyCode == Keys.Enter) + { + WriteLine(String.Format("> {0}", DebuggerInput.Text)); + + try + { + ExecuteCommand(DebuggerInput.Text); + } + catch(Exception ex) + { + WriteLine(ex.Message); + } + + // Clear input line for next input + DebuggerInput.Text = String.Empty; + } + } + + private void ExecuteCommand(string command) + { + if (string.IsNullOrWhiteSpace(command)) + { + command = _lastCommand; + } + + string[] tokens = command.Trim().Split(' '); + + if (tokens.Length == 0) + { + // Nothing to do + return; + } + + switch (tokens[0].ToLowerInvariant()) + { + case ".": // redisplay status + DisplayCurrentCode(); + PrintIOPStatus(); + PrintCPStatus(); + break; + + case "i": // single step IOP + StartExecution(true, false, false); + StopExecution(); // wait for exec to finish + DisplayCurrentCode(); + PrintIOPStatus(); + break; + + case "s": // single step CP + _stepCount = 0; + StartExecution(false, true, false); + StopExecution(); // wait for exec to finish + DisplayCurrentCode(); + PrintCPStatus(); + break; + + case "m": // single step Mesa macrocode + StartExecution(false, false, true); + StopExecution(); // wait for exec to finish + DisplayCurrentCode(); + PrintMesaStatus(); + break; + + case "g": // run system + StartExecution(false, false, false /* normal execution */); + break; + + case "r": // reset system + _system.Reset(); + WriteLine("System reset."); + break; + + case "tpc": // print TPC registers + DisplayTPCRegisters(); + break; + + case "task": // select Task to debug (skips over microcode execution for other tasks while single-stepping) + switch (tokens.Length) + { + case 2: + if (tokens[1].ToLowerInvariant() != "all") + { + // Step through a specific task only. + _debugTask = (TaskType)Enum.Parse(typeof(TaskType), tokens[1], true); + _debugSpecificTask = true; + } + else + { + // Step through all tasks + _debugSpecificTask = false; + } + break; + + default: + WriteLine("task "); + break; + } + break; + + case "id": // dump IOP memory + switch(tokens.Length) + { + case 2: + DumpMemory(Convert.ToUInt16(tokens[1], 16), 1, null); + break; + + case 3: + DumpMemory(Convert.ToUInt16(tokens[1], 16), Convert.ToUInt16(tokens[2], 16), null); + break; + + case 4: + DumpMemory(Convert.ToUInt16(tokens[1], 16), Convert.ToUInt16(tokens[2], 16), tokens[3]); + break; + + default: + WriteLine("id
[count] [file]"); + break; + } + break; + + case "cd": // dump CP memory + switch (tokens.Length) + { + case 2: + DumpCPMemory(Convert.ToUInt32(tokens[1], 16), 1, null); + break; + + case 3: + DumpCPMemory(Convert.ToUInt32(tokens[1], 16), Convert.ToUInt16(tokens[2], 16), null); + break; + + case 4: + DumpCPMemory(Convert.ToUInt32(tokens[1], 16), Convert.ToUInt16(tokens[2], 16), tokens[3]); + break; + + default: + WriteLine("cd
[count] [file]"); + break; + } + break; + + case "fd": // dump floppy sector + switch(tokens.Length) + { + case 4: + DumpFloppySector(Convert.ToUInt16(tokens[1], 10), + Convert.ToUInt16(tokens[2], 10), + Convert.ToUInt16(tokens[3], 10), + null); + break; + + case 5: + DumpFloppySector(Convert.ToUInt16(tokens[1], 10), + Convert.ToUInt16(tokens[2], 10), + Convert.ToUInt16(tokens[3], 10), + tokens[4]); + break; + + default: + WriteLine("id [file]"); + break; + } + break; + + case "u": // dump U register + switch (tokens.Length) + { + case 2: + DumpURegister(Convert.ToUInt16(tokens[1], 16)); + break; + + default: + WriteLine("u "); + break; + } + break; + + case "mapv": // Map virtual address to physical + switch (tokens.Length) + { + case 2: + TranslateVirtualAddress(Convert.ToInt32(tokens[1], 16)); + break; + + default: + WriteLine("mapv "); + break; + } + break; + + case "mapp": // Map physical address to virtual + switch (tokens.Length) + { + case 2: + TranslatePhysicalAddress(Convert.ToInt32(tokens[1], 16)); + break; + + default: + WriteLine("mapp "); + break; + } + break; + + case "mapd": // Dump map entry + switch (tokens.Length) + { + case 2: + DumpMapEntry(Convert.ToInt32(tokens[1], 16)); + break; + + default: + WriteLine("mapd "); + break; + } + break; + + case "mbs": // Set Mesa breakpoint + switch (tokens.Length) + { + case 2: + SetMesaBreakpoint(Convert.ToInt32(tokens[1], 16)); + break; + + default: + WriteLine("mbs "); + break; + } + break; + + case "mbc": // Clear Mesa breakpoint + switch (tokens.Length) + { + case 2: + ClearMesaBreakpoint(Convert.ToInt32(tokens[1], 16)); + break; + + default: + WriteLine("mbs "); + break; + } + break; + + case "da": // analyze disk + AnalyzeDisk(); + break; + + case "dt": // he can do stupid things + switch (tokens.Length) + { + case 3: + DumpTrack(Convert.ToInt32(tokens[1], 10), Convert.ToInt32(tokens[2], 10)); + break; + + default: + WriteLine("dt "); + break; + } + break; + + + case "save": // commit source annotations + _iopDebugger.Save(); + _cpDebugger.Save(); + WriteLine("Source annotations saved."); + break; + + case "clear": + DebugOutput.Clear(); + break; + + case "?": + case "help": + DisplayHelp(); + break; + + default: + WriteLine("?"); // for DMR + break; + } + + _lastCommand = command; + } + + private void StartExecution(bool singleStepIOP, bool singleStepCP, bool singleStepMesa) + { + _singleStepIOP = singleStepIOP; + _singleStepCP = singleStepCP; + _singleStepMesa = singleStepMesa; + + SystemExecutionContext context = + new SystemExecutionContext(StepCallback8085, StepCallbackCP, StepCallbackMesa, ErrorCallback); + _system.StartExecution(context); + + if (!_singleStepCP && !_singleStepIOP && !_singleStepMesa) + { + WriteLine("System started."); + } + } + + private void StopExecution() + { + _system.StopExecution(); + WriteLine("System stopped."); + } + + private bool StepCallback8085() + { + bool stopExecution = _singleStepIOP; + + // Check for execution breakpoints + if (BreakpointManager.TestBreakpoint(BreakpointProcessor.IOP, BreakpointType.Execution, _system.IOP.CPU.PC)) + { + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("* IOP Execution breakpoint hit at PC=${0:x4} *", _system.IOP.CPU.PC)); + stopExecution = true; + } + + // IOP processor still running? + if (_system.IOP.CPU.Halted) + { + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("* 8085 halted at PC=${0:x4} *", _system.IOP.CPU.PC)); + stopExecution = true; + } + + return stopExecution; + } + + private bool StepCallbackCP() + { + bool stopExecution = false; + + if (!_debugSpecificTask) + { + // Stop after every step + stopExecution = _singleStepCP; + } + else + { + // + // Stop only if the current task is the task we're debugging or we've exhausted our cycle count + // waiting for the task to wake up again. + // + _stepCount++; + + stopExecution = _singleStepCP && (_system.CP.CurrentTask == _debugTask || _stepCount > 1000); + + if (_stepCount > 1000) + { + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("Timeout waiting for task {0} to wake.", _debugTask)); + } + } + + if (!_system.CP.IOPWait) + { + // Check for execution breakpoints + int tpc = _system.CP.TPC[(int)_system.CP.CurrentTask]; + if (BreakpointManager.TestBreakpoint(BreakpointProcessor.CP, BreakpointType.Execution, (ushort)tpc)) + { + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("* CP Execution breakpoint hit at TPC=0x{0:x3} *", tpc)); + stopExecution = true; + } + } + + return stopExecution; + } + + private bool StepCallbackMesa() + { + bool stopExecution = _singleStepMesa; + + if (!_system.CP.IOPWait) + { + // Check for execution breakpoints + int mesaPC = ((((_system.CP.RH[5] & 0xf) << 16) | _system.CP.ALU.R[5]) << 1) | (_system.CP.PC16 ? 1 : 0); + if (BreakpointManager.TestBreakpoint(BreakpointProcessor.Mesa, BreakpointType.Execution, mesaPC)) + { + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("* Mesa Execution breakpoint hit at PC=0x{0:x6} *", mesaPC)); + stopExecution = true; + } + } + + return stopExecution; + } + + private void ErrorCallback(Exception e) + { + // TODO: be more helpful. + BeginInvoke(new StatusDelegate(RefreshPostExecution), String.Format("* Execution Error {0} *", e.Message)); + } + + + /// + /// Invoked on the UI thread + /// + /// + private void RefreshPostExecution(string message) + { + PrintIOPStatus(); + WriteLine(String.Empty); + PrintCPStatus(); + + WriteLine(message); + DisplayCurrentCode(); + } + + private void DumpMemory(ushort address, ushort length, string outFile) + { + int byteNum = 0; + StringBuilder line = new StringBuilder(); + line.AppendFormat("{0:x4}: ", address); + + FileStream fs = null; + + if (outFile != null) + { + fs = new FileStream(outFile, FileMode.Create, FileAccess.Write); + } + + for (ushort i = address; i < address + length; i++) + { + byte val = _system.IOP.Memory.ReadByte(i); + + line.AppendFormat("{0:x2} ", val); + + byteNum++; + if ((byteNum % 16) == 0) + { + WriteLine(line.ToString()); + line.Clear(); + line.AppendFormat("{0:x4}: ", i + 1); + byteNum = 0; + } + + if (fs != null) + { + fs.WriteByte(val); + } + } + + if (byteNum > 0) + { + WriteLine(line.ToString()); + } + + if (fs != null) + { + fs.Close(); + } + } + + private void DumpCPMemory(uint address, uint length, string outFile) + { + int byteNum = 0; + StringBuilder line = new StringBuilder(); + line.AppendFormat("{0:x5}: ", address); + + FileStream fs = null; + + if (outFile != null) + { + fs = new FileStream(outFile, FileMode.Create, FileAccess.Write); + } + + for (uint i = address; i < address + length; i++) + { + bool valid = false; + ushort val = _system.MemoryController.DebugMemory.ReadWord((int)i, out valid); + + line.AppendFormat("{0:x4} ", val); + + byteNum++; + if ((byteNum % 16) == 0) + { + WriteLine(line.ToString()); + line.Clear(); + line.AppendFormat("{0:x5}: ", i + 1); + byteNum = 0; + } + + if (fs != null) + { + // fs.WriteByte(val); + } + } + + if (byteNum > 0) + { + WriteLine(line.ToString()); + } + + if (fs != null) + { + fs.Close(); + } + } + + private void DumpFloppySector(int cylinder, int head, int sector, string outFile) + { + int byteNum = 0; + StringBuilder line = new StringBuilder(); + line.AppendFormat("{0:x4}: ", 0); + + FloppyDisk disk = _system.IOP.FloppyController.Drive.Disk; + + if (cylinder < 0 || cylinder > 76) + { + WriteLine("Invalid cylinder spec."); + return; + } + + if (head < 0 || head > 1) + { + WriteLine("Invalid head spec."); + return; + } + + if (disk == null) + { + WriteLine("No disk loaded."); + } + else + { + Sector sectorData = disk.GetSector(cylinder, head, sector - 1); + WriteLine(String.Format("Sector format {0}, length {1}", sectorData.Format, sectorData.Data.Length)); + + FileStream fs = null; + + if (outFile != null) + { + fs = new FileStream(outFile, FileMode.Create, FileAccess.Write); + } + + for (int i = 0; i < sectorData.Data.Length; i++) + { + byte val = sectorData.Data[i]; + + line.AppendFormat("{0:x2} ", val); + + byteNum++; + if ((byteNum % 16) == 0) + { + WriteLine(line.ToString()); + line.Clear(); + line.AppendFormat("{0:x4}: ", i + 1); + byteNum = 0; + } + + if (fs != null) + { + fs.WriteByte(val); + } + } + + if (byteNum > 0) + { + WriteLine(line.ToString()); + } + + if (fs != null) + { + fs.Close(); + } + } + } + + private void DumpURegister(int regNum) + { + if (regNum < 0 || regNum > 255) + { + WriteLine("Invalid U regster."); + return; + } + + WriteLine(String.Format("U{0:x2}=0x{1:x4}", regNum, _system.CP.U[regNum])); + } + + private void DumpMapEntry(int address) + { + if (address < 0x10000 || address > 0x13fff) + { + WriteLine("Invalid address."); + return; + } + + bool valid = false; + ushort entryWord = _system.MemoryController.DebugMemory.ReadWord(address, out valid); + + MapEntry entry = new MapEntry(entryWord); + + WriteLine(String.Format("Map entry at 0x{0:x5} - {1}", address, entry.ToString())); + } + + private void TranslateVirtualAddress(int vAddress) + { + if (vAddress < 0 || vAddress > 0x1000000) + { + WriteLine("Invalid address."); + return; + } + + int mapAddr = 0x10000 + (vAddress >> 8); + int pageOffset = vAddress & 0xff; + + bool valid = false; + ushort entryWord = _system.MemoryController.DebugMemory.ReadWord(mapAddr, out valid); + + MapEntry entry = new MapEntry(entryWord); + + WriteLine(String.Format("Map entry at 0x{0:x5} - {1}", mapAddr, entry.ToString())); + WriteLine(String.Format("VA 0x{0:x6} maps to PA 0x{1:x5}", vAddress, pageOffset + (entry.PageNumber << 8))); + } + + private void TranslatePhysicalAddress(int pAddress) + { + if (pAddress < 0 || pAddress > _system.MemoryController.DebugMemory.Size - 1) + { + WriteLine("Invalid address."); + return; + } + + int found = 0; + + for (int mapAddr = 0x10000; mapAddr < 0x14000; mapAddr++) + { + bool valid = false; + ushort entryWord = _system.MemoryController.DebugMemory.ReadWord(mapAddr, out valid); + + MapEntry entry = new MapEntry(entryWord); + + if ((pAddress >> 8) == entry.PageNumber) + { + int vAddress = ((mapAddr & 0xffff) << 8) | (pAddress & 0xff); + WriteLine(String.Format("Map entry at 0x{0:x5} - {1}", mapAddr, entry.ToString())); + WriteLine(String.Format("PA 0x{0:x5} maps to VA 0x{1:x6}", pAddress, vAddress)); + found++; + } + } + + if (found == 0) + { + WriteLine("No map entry for physical address."); + } + } + + private struct MapEntry + { + public MapEntry(ushort entryWord) + { + PageNumber = ((entryWord & 0xf) << 8) | (entryWord >> 8); + DP = (entryWord & 0x80) != 0; + W = (entryWord & 0x40) != 0; + D = (entryWord & 0x20) != 0; + RP = (entryWord & 0x10) != 0; + } + + public int PageNumber; + public bool DP; // Dirty & Present + public bool W; // Write Protected + public bool D; // Dirty + public bool RP; // Referenced & Present + + public override string ToString() + { + return String.Format("Page 0x{0:x4} {1}{2}{3}{4}", + PageNumber, + DP ? "dp " : String.Empty, + W ? "w " : String.Empty, + D ? "d " : String.Empty, + RP ? "rp " : String.Empty); + } + } + + private void SetMesaBreakpoint(int address) + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointProcessor.Mesa, BreakpointType.Execution, address)); + } + + private void ClearMesaBreakpoint(int address) + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointProcessor.Mesa, BreakpointType.None, address)); + } + + private void AnalyzeDisk() + { + SA1000Drive drive = _system.HardDrive; + + WriteLine(String.Format("Drive is type {0}", drive.Type)); + + for (int cylinder = 0; cylinder < drive.Geometry.Cylinders; cylinder++) + { + for (int head = 0; head < drive.Geometry.Heads; head++) + { + int sector = -1; + int field = 0; + + for (int word = 0; word < drive.WordsPerTrack; word++) + { + // + // Walk the track, looking for sector marks. + // There should be 16 sets of: + // - Header mark + // - Label mark + // - Data mark + // - CRC mark + // In exactly that order + // + // Verify the header data + // + uint data = drive.DebugRead(cylinder, head, word); + bool isAddressMark = (data & 0x10000) != 0; + bool isCRC = (data & 0x20000) != 0; + + switch (field) + { + case 0: + if (data == 0x1a141) // header + { + // This is the header mark, increment the sector count + // move to next field. + sector++; + field++; + } + else if (data == 0x1a143) // label / data + { + // Unexpected here. + WriteLine(String.Format("Unexpected label/data mark before header mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); + } + else if (data == 0x2beef) + { + /* + // Unexpected here. + WriteLine(String.Format("Unexpected CRC before header mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); */ + } + break; + + case 1: + if (data == 0x1a143) // label / data + { + // This is the label mark, move to next field. + field++; + } + else if (data == 0x1a141) // header + { + // Unexpected header mark here. + WriteLine(String.Format("Unexpected header mark after header mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); + } + else if (data == 0x2beef) + { + /* + // Unexpected here. + WriteLine(String.Format("Unexpected CRC after header mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); */ + } + break; + + case 2: + if (data == 0x1a143) // label / data + { + // This is the data mark, move to CRC. + field++; + } + else if (data == 0x1a141) // header + { + // Unexpected header mark here. + WriteLine(String.Format("Unexpected header mark after label mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); + } + else if (data == 0x2beef) + { + /* + // Unexpected here. + WriteLine(String.Format("Unexpected CRC after label mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); */ + } + break; + + case 3: + if (data == 0x2beef) // CRC + { + // This is the CRC, move back to header state. + field = 0; + } + else if (isAddressMark) + { + // Unexpected header mark here. + WriteLine(String.Format("Unexpected address mark after data mark at c/h/s (w) {0}/{1}/{2} ({3})", + cylinder, + head, + sector, + word)); + } + break; + } + } + + // Check sector count. Should be 15. + if (sector != 15) + { + WriteLine(String.Format("Unexpected sector count {0} at c/h {1}/{2}", + sector, + cylinder, + head)); + } + + } + } + } + + + private void DumpTrack(int cylinder, int head) + { + SA1000Drive drive = _system.HardDrive; + + int sector = 0; + int wordIndex = 0; + uint data = 0; + + List sectorData = new List(); + + // Print lead-in to first sector + while (wordIndex < drive.WordsPerTrack) + { + data = drive.DebugRead(cylinder, head, wordIndex); + wordIndex++; + + if (data == 0x1a141) + { + break; + } + else + { + sectorData.Add(data); + } + } + + WriteLine("Lead-in to sector 0:"); + PrintSectorData(sectorData); + + sectorData.Clear(); + + // Print sector data + while (wordIndex < drive.WordsPerTrack) + { + data = drive.DebugRead(cylinder, head, wordIndex); + wordIndex++; + + if (data == 0x1a141 || wordIndex == drive.WordsPerTrack - 1) + { + WriteLine(String.Format("Sector {0}:", sector)); + PrintSectorData(sectorData); + sectorData.Clear(); + + sector++; + } + else + { + sectorData.Add(data); + } + + } + } + + private void PrintSectorData(List data) + { + + int byteNum = 0; + StringBuilder line = new StringBuilder(); + line.AppendFormat("000: "); + + for (int i = 0; i < data.Count; i++) + { + line.AppendFormat("{0:x5} ", data[i]); + + byteNum++; + if ((byteNum % 16) == 0) + { + WriteLine(line.ToString()); + line.Clear(); + line.AppendFormat("{0:x3}: ", i + 1); + byteNum = 0; + } + } + + if (byteNum > 0) + { + WriteLine(line.ToString()); + } + } + + + private void DisplayCurrentCode() + { + _iopDebugger.DisplayCurrentCode(); + _cpDebugger.DisplayCurrentCode(); + } + + private void DisplayHelp() + { + WriteLine(@" + ?, help - Display this message. + . - Display IOP and CP status. + i - Single step IOP. + s - Single step CP. + m - Single step macrocode. + g - Start/continue system execution. + tpc - Display TPC registers. + task - Select Task to debug (skips over microcode execution + for other tasks while single-stepping.) + id
[count] [toFile] - Dump IOP memory. + cd
[count] [toFile] - Dump CP memory. + fd - Dump floppy sector. + u - Display specified U register. + mapv - Map virtual address to physical. + mapp - Map physical address to virtual. + mapd - Dump map entry at specified map address. + mbs - Set Macroinstruction breakpoint. + mbc - Clear Macroinstruction breakpoint. + dt - Dump hard disk track. + save - Commit source annotations. + clear - Clear debugger scrollback. + " + ); + } + + private void WriteLine(string line) + { + DebugOutput.Text += line + "\r\n"; + DebugOutput.Select(DebugOutput.TextLength - 1, 1); + DebugOutput.ScrollToCaret(); + } + + private void Write(string line) + { + DebugOutput.Text += line; + DebugOutput.Select(DebugOutput.TextLength - 1, 1); + DebugOutput.ScrollToCaret(); + } + + private void PrintIOPStatus() + { + WriteLine(String.Format("IOP PC=${0:x4} SP=${1:x4} AF=${2:x4} BC=${3:x4} DE=${4:x4} HL=${5:x4}", + _system.IOP.CPU.PC, _system.IOP.CPU.SP, _system.IOP.CPU.AF, _system.IOP.CPU.BC, _system.IOP.CPU.DE, _system.IOP.CPU.HL)); + WriteLine(String.Format("Flags {0}", GetStringForFlags(_system.IOP.CPU.F))); + + SourceEntry entry = _iopDebugger.SourceMap.GetSourceForAddress(_system.IOP.CPU.PC); + string symbolName = null; + string currentSymbol = String.Empty; + + if (entry != null) + { + if (entry.SymbolNames.Length == 0 || + entry.SymbolNames[0] == "*none*" || // TODO: move to constant + entry.Address != _system.IOP.CPU.PC) + { + // No symbol name associated with this entry, find the nearest. + SourceEntry symbolEntry = _iopDebugger.SourceMap.GetNearestSymbolForAddress(_system.IOP.CPU.PC); + + if (symbolEntry != null) + { + symbolName = String.Format("{0}+${1:x}", symbolEntry.SymbolNames[0], _system.IOP.CPU.PC - symbolEntry.Address); + } + } + else + { + symbolName = entry.SymbolNames[0]; + } + + if (symbolName != null) + { + currentSymbol = String.Format("{0},{1} line {2}", symbolName, entry.SourcePath, entry.LineNumber); + } + } + + WriteLine(String.Format("${0:x4} ({1})\r\n {2}", _system.IOP.CPU.PC, currentSymbol, _system.IOP.CPU.Disassemble(_system.IOP.CPU.PC))); + + // Update the IOP debugger's title bar with current MP value + _iopDebugger.Text = String.Format("IOP Debugger - MP {0}", _system.IOP.MiscIO.MPanelBlank ? "" : _system.IOP.MiscIO.MPanelValue.ToString()); + } + + private void PrintCPStatus() + { + int tpc = _system.CP.TPC[(int)_system.CP.CurrentTask]; + + SourceEntry symbolEntry = _cpDebugger.GetSymbolForAddress(tpc); + + string currentSymbol = String.Empty; + if (symbolEntry != null) + { + string symbolName = symbolEntry.SymbolNames.Length > 0 && + symbolEntry.SymbolNames[0] != "*none*" ? symbolEntry.SymbolNames[0] : String.Empty; + + currentSymbol = String.Format("{0},{1} line {2}", symbolName, symbolEntry.SourcePath, symbolEntry.LineNumber); + } + + WriteLine(String.Format("CP Task={0} TPC={1:x3} {2}", + _system.CP.CurrentTask, + tpc, + currentSymbol)); + + StringBuilder regString = new StringBuilder(); + for (int i = 0; i < 16; i++) + { + regString.AppendFormat(" R{0:x}=0x{1:x4} ", i, _system.CP.ALU.R[i]); + + if (((i+1) % 8) == 0) + { + WriteLine(regString.ToString()); + regString.Clear(); + } + } + + StringBuilder reghString = new StringBuilder(); + for (int i = 0; i < 16; i++) + { + reghString.AppendFormat(" RH{0:x}=0x{1:x2} ", i, _system.CP.RH[i]); + + if (((i + 1) % 8) == 0) + { + WriteLine(reghString.ToString()); + reghString.Clear(); + } + } + + WriteLine(reghString.ToString()); + + StringBuilder stackString = new StringBuilder(); + // By convention, R0 is TOS in Mesa. + stackString.AppendFormat("0x{0:x4} ", _system.CP.ALU.R[0]); + + for (int i = _system.CP.StackP; i > 0; i--) + { + stackString.AppendFormat("{0:x4} ", _system.CP.U[i]); + } + + WriteLine(String.Format(" stackP=0x{0:x1} stack: {1}", _system.CP.StackP, stackString)); + + WriteLine(String.Format( + " Q=0x{0:x4} MAR=0x{1:x5} MD=0x{2:x4} pc16={3} ibPtr={4} ibFront=0x{5:x2} ib[0]=0x{6:x2} ib[1]=0x{7:x2}", + _system.CP.ALU.Q, + _system.MemoryController.MAR, + _system.MemoryController.MD, + _system.CP.PC16 ? 1 : 0, + _system.CP.IBPtr, + _system.CP.IBFront, + _system.CP.IB[0], + _system.CP.IB[1])); + + WriteLine(String.Format(" Z={0} N={1} Nb={2} Pg={3} C={4} O={5}", + _system.CP.ALU.Zero, + _system.CP.ALU.Neg, + _system.CP.ALU.NibCarry, + _system.CP.ALU.PgCarry, + _system.CP.ALU.CarryOut, + _system.CP.ALU.Overflow)); + + Microinstruction inst = new Microinstruction(_system.CP.MicrocodeRam[tpc]); + int nia = inst.INIA | _system.CP.NIAModifier; + + WriteLine(String.Format("{0:x3} {1:x12} - {2} (NIA={3:x3}) [c{4}]", + tpc, + _system.CP.MicrocodeRam[tpc], + inst.Disassemble(_system.CP.Cycle), + nia, + _system.CP.Cycle)); + } + + private void PrintMesaStatus() + { + int pc = ((((_system.CP.RH[5] & 0xf) << 16) | _system.CP.ALU.R[5]) << 1) | (_system.CP.PC16 ? 1 : 0); + + WriteLine(String.Format("Mesa PC=0x{0:x5} (physical address 0x{1:x5})", + pc, + pc >> 1)); + + StringBuilder stackString = new StringBuilder(); + + // By convention, R0 is TOS in Mesa. + stackString.AppendFormat("0x{0:x4} ", _system.CP.ALU.R[0]); + for (int i = _system.CP.StackP; i > 0; i--) + { + stackString.AppendFormat("0x{0:x4} ", _system.CP.U[i]); + } + + WriteLine(String.Format(" stackP=0x{0:x1} stack: {1}", _system.CP.StackP, stackString)); + + WriteLine(String.Format( + " ibPtr={0} ibFront=0x{1:x2} ib[0]=0x{2:x2} ib[1]=0x{3:x2}", + _system.CP.IBPtr, + _system.CP.IBFront, + _system.CP.IB[0], + _system.CP.IB[1])); + + // Since this breakpoint should always happen the microinstruction after an IBDisp has taken place, + // the TPC is always pointing to the dispatch address for the bytecode, which is not coincidentally + // the bytecode itself. (The value of ibFront that caused the dispatch has since been discarded, or + // we'd use that.) + byte byteCode = (byte)(_system.CP.TPC[(int)TaskType.Emulator]); + MacroInstruction mInst = MacroInstruction.GetInstruction(MacroType.Lisp, byteCode); + + string operand = string.Empty; + switch (mInst.Operand) + { + case MacroOperand.None: + // No operands. + break; + + case MacroOperand.Byte: + operand = String.Format("0x{0:x2}", _system.CP.IBFront); + break; + + case MacroOperand.SignedByte: + operand = String.Format("{0}", (sbyte)_system.CP.IBFront); + break; + + case MacroOperand.Pair: + operand = String.Format("0x{0:x1},,0x{1:x1}", _system.CP.IBFront >> 4, _system.CP.IBFront & 0xf); + break; + + case MacroOperand.TwoByte: + operand = String.Format("0x{0:x2},,0x{1:x2}", + _system.CP.IBFront, + _system.CP.IBPtr == IBState.Word ? _system.CP.IB[1] : _system.CP.IB[0]); + break; + + case MacroOperand.Word: + operand = String.Format("0x{0:x4}", + (_system.CP.IBFront << 8) | (_system.CP.IBPtr == IBState.Word ? _system.CP.IB[1] : _system.CP.IB[0])); + break; + } + + WriteLine(String.Format("Bytecode 0x{0:x2} - {1} {2}", byteCode, mInst.Mnemonic, operand)); + } + + private void DisplayTPCRegisters() + { + for(int i=0;i<8;i++) + { + WriteLine(String.Format("{0} - 0x{1:x3}", (TaskType)i, _system.CP.TPC[i])); + } + } + + private void InjectKeystroke(int keycode) + { + _system.IOP.Keyboard.KeyDown((KeyCode)keycode); + _system.IOP.Keyboard.KeyUp((KeyCode)keycode); + } + + private string GetStringForFlags(byte f) + { + string flags = String.Empty; + + if (f == 0) + { + flags = "NONE"; + return flags; + } + + if ((f & 0x80) != 0) + { + flags += "S "; + } + + if ((f & 0x40) != 0) + { + flags += "Z "; + } + + if ((f & 0x10) != 0) + { + flags += "AC "; + } + + if ((f & 0x04) != 0) + { + flags += "P "; + } + + if ((f & 0x01) != 0) + { + flags += "CY "; + } + + return flags; + } + + private void OnFormClosing(object sender, FormClosingEventArgs e) + { + _iopDebugger.Close(); + _cpDebugger.Close(); + } + + private DSystem _system; + + private string _lastCommand; + private bool _singleStepIOP; + private bool _singleStepCP; + private bool _singleStepMesa; + private TaskType _debugTask; + private bool _debugSpecificTask; + private int _stepCount; + private DebuggerReason _reason; + private string _entryMessage; + + private IOPDebugger _iopDebugger; + private CPDebugger _cpDebugger; + + private delegate void StatusDelegate(string message); + + + } +} diff --git a/D/Debugger/DebuggerMain.resx b/D/Debugger/DebuggerMain.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/Debugger/DebuggerMain.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/Debugger/IOPDebugger.Designer.cs b/D/Debugger/IOPDebugger.Designer.cs new file mode 100644 index 0000000..3a302c3 --- /dev/null +++ b/D/Debugger/IOPDebugger.Designer.cs @@ -0,0 +1,142 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.Debugger +{ + partial class IOPDebugger + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); + this.IOPSourceBox = new System.Windows.Forms.GroupBox(); + this.SourceFiles = new System.Windows.Forms.ListBox(); + this.SourceDisplay = new D.Debugger.SourceDisplay(); + this.IOPSourceBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.SourceDisplay)).BeginInit(); + this.SuspendLayout(); + // + // IOPSourceBox + // + this.IOPSourceBox.Controls.Add(this.SourceFiles); + this.IOPSourceBox.Controls.Add(this.SourceDisplay); + this.IOPSourceBox.Location = new System.Drawing.Point(7, 3); + this.IOPSourceBox.Name = "IOPSourceBox"; + this.IOPSourceBox.Size = new System.Drawing.Size(837, 725); + this.IOPSourceBox.TabIndex = 2; + this.IOPSourceBox.TabStop = false; + this.IOPSourceBox.Text = "IOP Source"; + // + // SourceFiles + // + this.SourceFiles.FormattingEnabled = true; + this.SourceFiles.Location = new System.Drawing.Point(7, 20); + this.SourceFiles.Name = "SourceFiles"; + this.SourceFiles.Size = new System.Drawing.Size(139, 693); + this.SourceFiles.TabIndex = 1; + this.SourceFiles.DoubleClick += new System.EventHandler(this.SourceFiles_DoubleClick); + // + // SourceDisplay + // + this.SourceDisplay.AllowUserToAddRows = false; + this.SourceDisplay.AllowUserToDeleteRows = false; + this.SourceDisplay.AllowUserToResizeColumns = false; + this.SourceDisplay.AllowUserToResizeRows = false; + this.SourceDisplay.BorderStyle = System.Windows.Forms.BorderStyle.None; + this.SourceDisplay.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.SingleVertical; + this.SourceDisplay.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft; + dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window; + dataGridViewCellStyle2.Font = new System.Drawing.Font("Consolas", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText; + dataGridViewCellStyle2.NullValue = null; + dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; + dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; + dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False; + this.SourceDisplay.DefaultCellStyle = dataGridViewCellStyle2; + this.SourceDisplay.Location = new System.Drawing.Point(152, 19); + this.SourceDisplay.Name = "SourceDisplay"; + this.SourceDisplay.RowHeadersVisible = false; + this.SourceDisplay.RowHeadersWidthSizeMode = System.Windows.Forms.DataGridViewRowHeadersWidthSizeMode.DisableResizing; + this.SourceDisplay.RowTemplate.Height = 18; + this.SourceDisplay.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.SourceDisplay.Size = new System.Drawing.Size(679, 694); + this.SourceDisplay.TabIndex = 0; + this.SourceDisplay.VirtualMode = true; + // + // IOPDebugger + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSize = true; + this.ClientSize = new System.Drawing.Size(851, 732); + this.ControlBox = false; + this.Controls.Add(this.IOPSourceBox); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; + this.KeyPreview = true; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "IOPDebugger"; + this.Text = "IOP Debugger - MP {0}"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.IOPDebugger_FormClosed); + this.IOPSourceBox.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)(this.SourceDisplay)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.GroupBox IOPSourceBox; + private SourceDisplay SourceDisplay; + private System.Windows.Forms.ListBox SourceFiles; + } +} \ No newline at end of file diff --git a/D/Debugger/IOPDebugger.cs b/D/Debugger/IOPDebugger.cs new file mode 100644 index 0000000..c8a05f7 --- /dev/null +++ b/D/Debugger/IOPDebugger.cs @@ -0,0 +1,119 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +namespace D.Debugger +{ + public partial class IOPDebugger : Form + { + public IOPDebugger(DSystem system) + { + InitializeComponent(); + + _system = system; + + _sourceMap = new SourceMap("8085 ROM", "IOP\\Source\\SourceMap.txt", "IOP\\Source"); + + SourceDisplay.SetSourceRoot(Path.Combine("IOP", "Source")); + SourceDisplay.AttachMap(_sourceMap); + + PopulateSourceList(); + + DisplayCurrentCode(); + } + + public SourceMap SourceMap + { + get { return _sourceMap; } + } + + public void DisplayCurrentCode() + { + SourceEntry entry = _sourceMap.GetSourceForAddress(_system.IOP.CPU.PC); + + if (entry != null && !string.IsNullOrWhiteSpace(entry.SourcePath)) + { + SourceDisplay.SelectSourceEntry(entry, false, true /* iop */); + + + // Find the source entry in the file list and select it. + for (int i = 0; i < SourceFiles.Items.Count; i++) + { + if ((string)SourceFiles.Items[i] == entry.SourcePath) + { + SourceFiles.SelectedIndex = i; + break; + } + } + } + else + { + // Should show disassembly instead + } + } + + public void Save() + { + _sourceMap.Save(); + } + + private void PopulateSourceList() + { + SourceFiles.Items.Clear(); + + // TODO: factor out path generation + IEnumerable files = Directory.EnumerateFiles(Path.Combine("IOP", "Source"), "*.asm,v", SearchOption.TopDirectoryOnly); + + foreach(string file in files) + { + SourceFiles.Items.Add(Path.GetFileName(file)); + } + } + + private void SourceFiles_DoubleClick(object sender, EventArgs e) + { + SourceDisplay.SelectSourceEntry(new SourceEntry((string)SourceFiles.SelectedItem, new string[] { }, 0, 1), false, true /* iop */); + } + + private void IOPDebugger_FormClosed(object sender, FormClosedEventArgs e) + { + _sourceMap.Save(); + } + + private SourceMap _sourceMap; + + private DSystem _system; + + + } +} diff --git a/D/Debugger/IOPDebugger.resx b/D/Debugger/IOPDebugger.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/Debugger/IOPDebugger.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/Debugger/LoadMapDialog.Designer.cs b/D/Debugger/LoadMapDialog.Designer.cs new file mode 100644 index 0000000..1115884 --- /dev/null +++ b/D/Debugger/LoadMapDialog.Designer.cs @@ -0,0 +1,235 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.Debugger +{ + partial class LoadMapDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.CurrentLoadsList = new System.Windows.Forms.ListBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.LoadNameText = new System.Windows.Forms.TextBox(); + this.label1 = new System.Windows.Forms.Label(); + this.LoadStartBox = new System.Windows.Forms.TextBox(); + this.LoadEndBox = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.CreateLoadButton = new System.Windows.Forms.Button(); + this.CancelCreateButton = new System.Windows.Forms.Button(); + this.AddButton = new System.Windows.Forms.Button(); + this.CancelAddButton = new System.Windows.Forms.Button(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.SuspendLayout(); + // + // groupBox1 + // + this.groupBox1.Controls.Add(this.CancelAddButton); + this.groupBox1.Controls.Add(this.AddButton); + this.groupBox1.Controls.Add(this.CurrentLoadsList); + this.groupBox1.Location = new System.Drawing.Point(5, 5); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(171, 137); + this.groupBox1.TabIndex = 0; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Add to Existing Microcode Load"; + // + // CurrentLoadsList + // + this.CurrentLoadsList.FormattingEnabled = true; + this.CurrentLoadsList.Location = new System.Drawing.Point(7, 16); + this.CurrentLoadsList.Name = "CurrentLoadsList"; + this.CurrentLoadsList.Size = new System.Drawing.Size(156, 82); + this.CurrentLoadsList.TabIndex = 0; + this.CurrentLoadsList.SelectedIndexChanged += new System.EventHandler(this.OnSelectionChanged); + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.CancelCreateButton); + this.groupBox2.Controls.Add(this.CreateLoadButton); + this.groupBox2.Controls.Add(this.label3); + this.groupBox2.Controls.Add(this.label2); + this.groupBox2.Controls.Add(this.LoadEndBox); + this.groupBox2.Controls.Add(this.LoadStartBox); + this.groupBox2.Controls.Add(this.label1); + this.groupBox2.Controls.Add(this.LoadNameText); + this.groupBox2.Location = new System.Drawing.Point(182, 5); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(178, 137); + this.groupBox2.TabIndex = 1; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Add to New Microcode Load"; + // + // LoadNameText + // + this.LoadNameText.Location = new System.Drawing.Point(51, 17); + this.LoadNameText.Name = "LoadNameText"; + this.LoadNameText.Size = new System.Drawing.Size(103, 20); + this.LoadNameText.TabIndex = 0; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(10, 20); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(38, 13); + this.label1.TabIndex = 1; + this.label1.Text = "Name:"; + // + // LoadStartBox + // + this.LoadStartBox.Location = new System.Drawing.Point(51, 44); + this.LoadStartBox.Name = "LoadStartBox"; + this.LoadStartBox.Size = new System.Drawing.Size(100, 20); + this.LoadStartBox.TabIndex = 2; + // + // LoadEndBox + // + this.LoadEndBox.Location = new System.Drawing.Point(51, 71); + this.LoadEndBox.Name = "LoadEndBox"; + this.LoadEndBox.Size = new System.Drawing.Size(100, 20); + this.LoadEndBox.TabIndex = 3; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(10, 47); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(32, 13); + this.label2.TabIndex = 4; + this.label2.Text = "Start:"; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(10, 74); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(29, 13); + this.label3.TabIndex = 5; + this.label3.Text = "End:"; + // + // CreateLoadButton + // + this.CreateLoadButton.Location = new System.Drawing.Point(13, 104); + this.CreateLoadButton.Name = "CreateLoadButton"; + this.CreateLoadButton.Size = new System.Drawing.Size(75, 23); + this.CreateLoadButton.TabIndex = 6; + this.CreateLoadButton.Text = "Create"; + this.CreateLoadButton.UseVisualStyleBackColor = true; + this.CreateLoadButton.Click += new System.EventHandler(this.CreateLoadButton_Click); + // + // CancelCreateButton + // + this.CancelCreateButton.Location = new System.Drawing.Point(94, 104); + this.CancelCreateButton.Name = "CancelCreateButton"; + this.CancelCreateButton.Size = new System.Drawing.Size(75, 23); + this.CancelCreateButton.TabIndex = 7; + this.CancelCreateButton.Text = "Cancel"; + this.CancelCreateButton.UseVisualStyleBackColor = true; + this.CancelCreateButton.Click += new System.EventHandler(this.CancelCreateButton_Click); + // + // AddButton + // + this.AddButton.Location = new System.Drawing.Point(7, 104); + this.AddButton.Name = "AddButton"; + this.AddButton.Size = new System.Drawing.Size(75, 23); + this.AddButton.TabIndex = 7; + this.AddButton.Text = "Add"; + this.AddButton.UseVisualStyleBackColor = true; + this.AddButton.Click += new System.EventHandler(this.AddButton_Click); + // + // CancelAddButton + // + this.CancelAddButton.Location = new System.Drawing.Point(88, 104); + this.CancelAddButton.Name = "CancelAddButton"; + this.CancelAddButton.Size = new System.Drawing.Size(75, 23); + this.CancelAddButton.TabIndex = 8; + this.CancelAddButton.Text = "Cancel"; + this.CancelAddButton.UseVisualStyleBackColor = true; + this.CancelAddButton.Click += new System.EventHandler(this.CancelAddButton_Click); + // + // LoadMapDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(365, 146); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Name = "LoadMapDialog"; + this.Text = "Select Microcode Load For File"; + this.groupBox1.ResumeLayout(false); + this.groupBox2.ResumeLayout(false); + this.groupBox2.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button CancelAddButton; + private System.Windows.Forms.Button AddButton; + private System.Windows.Forms.ListBox CurrentLoadsList; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Button CancelCreateButton; + private System.Windows.Forms.Button CreateLoadButton; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.TextBox LoadEndBox; + private System.Windows.Forms.TextBox LoadStartBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox LoadNameText; + } +} \ No newline at end of file diff --git a/D/Debugger/LoadMapDialog.cs b/D/Debugger/LoadMapDialog.cs new file mode 100644 index 0000000..364937f --- /dev/null +++ b/D/Debugger/LoadMapDialog.cs @@ -0,0 +1,140 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Windows.Forms; + +namespace D.Debugger +{ + public partial class LoadMapDialog : Form + { + /// + /// Provide basic UI for adding new entries. + /// TODO: It's kind of ugly that this is directly modifying input parameters; likely there should be a + /// singleton LoadMap that maintains all of this that can be used to modify things. + /// + /// + /// + /// + /// + public LoadMapDialog(string newFilePath, MicrocodeLoadMap loadMap, List sourceMaps, ulong[] microcodeRAM) + { + InitializeComponent(); + + _newFilePath = newFilePath; + _loadMap = loadMap; + _sourceMaps = sourceMaps; + _microcodeRAM = microcodeRAM; + + _selectedMap = null; + + // + // Populate the existing map list + // + foreach (SourceMap s in sourceMaps) + { + CurrentLoadsList.Items.Add(s.MapName); + } + + CurrentLoadsList.ClearSelected(); + + DialogResult = DialogResult.Cancel; + } + + public SourceMap SelectedMap + { + get { return _selectedMap; } + } + + private void OnSelectionChanged(object sender, EventArgs e) + { + AddButton.Enabled = CurrentLoadsList.SelectedItem != null; + } + + private void CancelAddButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void CancelCreateButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + this.Close(); + } + + private void AddButton_Click(object sender, EventArgs e) + { + // + // Add the file to the selected map. + // + _selectedMap = _sourceMaps[CurrentLoadsList.SelectedIndex]; + + _selectedMap.AddSourceFile(_newFilePath); + DialogResult = DialogResult.OK; + this.Close(); + } + + private void CreateLoadButton_Click(object sender, EventArgs e) + { + try + { + LoadMapEntry newEntry = _loadMap.AddEntry( + LoadNameText.Text, + Convert.ToInt32(LoadStartBox.Text.Trim(), 16), + Convert.ToInt32(LoadEndBox.Text.Trim(), 16), + _microcodeRAM + ); + + _selectedMap = new SourceMap( + newEntry.Name, + Path.Combine("CP", "Source", newEntry.MapName), + Path.Combine("CP", "Source")); // TODO: define this path somewheres. + + _sourceMaps.Add(_selectedMap); + + DialogResult = DialogResult.OK; + this.Close(); + } + catch(Exception ex) + { + MessageBox.Show("Error: {0}", ex.Message); + } + } + + private SourceMap _selectedMap; + + private string _newFilePath; + private MicrocodeLoadMap _loadMap; + private List _sourceMaps; + private ulong[] _microcodeRAM; + } +} diff --git a/D/Debugger/LoadMapDialog.resx b/D/Debugger/LoadMapDialog.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/Debugger/LoadMapDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/Debugger/MicrocodeDisplay.cs b/D/Debugger/MicrocodeDisplay.cs new file mode 100644 index 0000000..1366bfe --- /dev/null +++ b/D/Debugger/MicrocodeDisplay.cs @@ -0,0 +1,202 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using System; +using System.Windows.Forms; + +namespace D.Debugger +{ + /// + /// Presents a view of microcode disassembly, with addresses and breakpoints. + /// + /// MicrocodeDisplay provides a three-column view: + /// + /// | Breakpoint | Address | Disassembly | + /// ------------------------------------------------------------------------- + /// + /// Breakpoint is editable (to allow setting breakpoints) + /// + /// TODO: This is pretty similar in functionality to the IOP source view. I'm + /// sure there's some code that could be shared... + /// + /// + public class MicrocodeDisplay : DataGridView + { + public MicrocodeDisplay() + { + this.VirtualMode = true; + this.RowHeadersVisible = false; + this.ReadOnly = false; + + AddCheckboxColumn("B", DataGridViewAutoSizeColumnMode.ColumnHeader); + AddColumn("Address", true, DataGridViewAutoSizeColumnMode.ColumnHeader); + AddColumn("Disassembly", true, DataGridViewAutoSizeColumnMode.Fill); + } + + /// + /// Returns the address selected in the disassembly. Since the row index and the + /// address are identical, we just return the selected row index (if any). + /// Returns -1 if nothing is selected. + /// + public int SelectedAddress + { + get { return this.SelectedCells.Count > 0 ? this.SelectedCells[0].RowIndex : -1; } + } + + public void AttachCP(CentralProcessor cp) + { + _cp = cp; + + this.RowCount = cp.MicrocodeRam.Length; + } + + public void SelectAddress(int address) + { + if (address < 0 || address > _cp.MicrocodeRam.Length) + { + throw new InvalidOperationException("Invalid address."); + } + + // + // Clear the current selection and move the selection to the first cell + // of the requested line. + // + this.ClearSelection(); + this.Rows[address].Selected = true; + this.CurrentCell = this.Rows[address].Cells[0]; + } + + protected override void OnCurrentCellDirtyStateChanged(EventArgs e) + { + if (IsCurrentCellDirty) + { + // + // Force checkbox changes to commit immediately (rather than + // the default, which is to commit them when focus leaves the cell, which + // is really annoying. + // + if (CurrentCell is DataGridViewCheckBoxCell) + { + CommitEdit(DataGridViewDataErrorContexts.Commit); + } + } + + base.OnCurrentCellDirtyStateChanged(e); + } + + protected override void OnCellValueChanged(DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0 || e.RowIndex > _cp.MicrocodeRam.Length) + { + base.OnCellValueChanged(e); + return; + } + + switch (e.ColumnIndex) + { + case 0: // breakpoint + { + // grab the check value + bool cellValue = (bool)this.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue; + + if (cellValue) + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointProcessor.CP, BreakpointType.Execution, (ushort)e.RowIndex)); + } + else + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(BreakpointProcessor.CP, BreakpointType.None, (ushort)e.RowIndex)); + } + + } + break; + } + + base.OnCellValueChanged(e); + } + + protected override void OnCellValueNeeded(DataGridViewCellValueEventArgs e) + { + if (e.RowIndex > _cp.MicrocodeRam.Length) + { + // Past end of microcode, nothing to do. + base.OnCellValueNeeded(e); + return; + } + + switch (e.ColumnIndex) + { + case 0: + { + ushort address = (ushort)e.RowIndex; + + e.Value = BreakpointManager.GetBreakpoint(BreakpointProcessor.CP, address) != BreakpointType.None; + } + break; + + case 1: + e.Value = String.Format("{0:x3}", e.RowIndex); + break; + + case 2: + e.Value = new Microinstruction(_cp.MicrocodeRam[e.RowIndex]).Disassemble(-1); + break; + + default: + throw new InvalidOperationException("Unhandled column."); + } + + base.OnCellValueNeeded(e); + } + + private void AddColumn(string name, bool readOnly, DataGridViewAutoSizeColumnMode sizeMode) + { + int index = this.Columns.Add(name, name); + + this.Columns[index].ReadOnly = readOnly; + this.Columns[index].Resizable = DataGridViewTriState.False; + this.Columns[index].SortMode = DataGridViewColumnSortMode.NotSortable; + this.Columns[index].AutoSizeMode = sizeMode; + } + + private void AddCheckboxColumn(string name, DataGridViewAutoSizeColumnMode sizeMode) + { + int index = this.Columns.Add(new DataGridViewCheckBoxColumn()); + + this.Columns[index].HeaderText = name; + this.Columns[index].ReadOnly = false; + this.Columns[index].Resizable = DataGridViewTriState.False; + this.Columns[index].SortMode = DataGridViewColumnSortMode.NotSortable; + this.Columns[index].AutoSizeMode = sizeMode; + } + + private CentralProcessor _cp; + } +} diff --git a/D/Debugger/MicrocodeLoadMap.cs b/D/Debugger/MicrocodeLoadMap.cs new file mode 100644 index 0000000..329a535 --- /dev/null +++ b/D/Debugger/MicrocodeLoadMap.cs @@ -0,0 +1,252 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace D.Debugger +{ + + public struct LoadMapEntry + { + public LoadMapEntry(string name, string mapName, int start, int end, byte[] hash) + { + Name = name; + MapName = mapName; + Start = start; + End = end; + Hash = hash; + } + + /// + /// A friendly name for this load (i.e. "Phase 0 Microcode") + /// + public string Name; + + /// + /// The file name for the source map for this load + /// + public string MapName; + + /// + /// Beginning of address range for microcode load + /// + public int Start; + + /// + /// End of range (inclusive) + /// + public int End; + + /// + /// MD5 hash of microcode memory for range + /// + public byte[] Hash; + } + + /// + /// MicrocodeLoadMap keeps track of a set of LoadMapEntries, each of which + /// specifies a map from a memory range + checksum to a source code mapping (i.e. symbol table). + /// This source code map is identical to the map used for the 8085 source map. + /// + /// This should in theory allow for more-or-less automagical mapping of whatever's in microcode + /// RAM to the appropriate source files (assuming I've done the gruntwork of actually doing the + /// mapping beforehand.) + /// + /// + public class MicrocodeLoadMap + { + public MicrocodeLoadMap() + { + _mapEntries = new List(); + } + + public LoadMapEntry AddEntry(string name, int start, int end, ulong[] microcodeRAM) + { + if (end <= start || start > microcodeRAM.Length || end > microcodeRAM.Length) + { + throw new InvalidOperationException("Invalid start/end parameters."); + } + + // + // Ensure no duplicate entries (by name, anyway...) + // + foreach(LoadMapEntry e in _mapEntries) + { + if (e.Name.ToLowerInvariant() == name.ToLowerInvariant()) + { + throw new InvalidOperationException("Duplicate map entry name."); + } + } + + // Generate a map name + string mapName = name + "_map.txt"; + + // If the map file doesn't exist, create it now. + + + // calculate the MD5 hash + byte[] hash = ComputeHash(start, end, microcodeRAM); + + LoadMapEntry newEntry = new LoadMapEntry(name, mapName, start, end, hash); + + _mapEntries.Add(newEntry); + + return newEntry; + } + + public List FindEntries(ulong[] microcodeRAM) + { + // + // Given the provided microcode RAM, Walk the entries we know about and + // see which ones match, if any. + // + List foundEntries = new List(); + + foreach(LoadMapEntry e in _mapEntries) + { + // + // Hash the memory range specified by this entry and see if it matches. + // + byte[] hash = ComputeHash(e.Start, e.End, microcodeRAM); + bool match = true; + + for (int i = 0; i < hash.Length; i++) + { + if (hash[i] != e.Hash[i]) + { + match = false; + break; + } + } + + if (match) + { + foundEntries.Add(e); + } + } + + return foundEntries; + } + + public void Save(string path) + { + using (StreamWriter sw = new StreamWriter(path)) + { + // + // Each entry looks like: + // + // ,,,, + // where: + // and are strings, + // and are hexadecimal values + // is written as a series of ascii hex digits + // + // empty lines or lines beginning with "#" are ignored. + // + // And that's it! + // + foreach (LoadMapEntry e in _mapEntries) + { + StringBuilder hashText = new StringBuilder(); + for (int i = 0; i < e.Hash.Length; i++) + { + hashText.AppendFormat("{0:x2}", e.Hash[i]); + } + + sw.WriteLine("{0},{1},{2:x3},{3:x3},{4}", e.Name, e.MapName, e.Start, e.End, hashText.ToString()); + } + } + } + + public void Load(string path) + { + using (StreamReader sr = new StreamReader(path)) + { + _mapEntries.Clear(); + + // + // See "Save" for the format we're dealing with here. + // + while(!sr.EndOfStream) + { + string line = sr.ReadLine(); + + if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) + { + continue; + } + + string[] tokens = line.Split(','); + + string hashString = tokens[4].Trim(); + byte[] hash = new byte[hashString.Length / 2]; + + for (int i = 0; i < hashString.Length; i += 2) + { + hash[i / 2] = Convert.ToByte(hashString.Substring(i, 2), 16); + } + + LoadMapEntry e = new LoadMapEntry( + tokens[0], // name + tokens[1], // mapname + Convert.ToInt32(tokens[2].Trim(), 16), // start + Convert.ToInt32(tokens[3].Trim(), 16), // end + hash); + + _mapEntries.Add(e); + } + } + } + + private byte[] ComputeHash(int start, int end, ulong[] microcodeRAM) + { + // + // Create a byte[] of the microcode data because why not. + // + byte[] microcodeBytes = new byte[(end - start) * 8]; + int microcodeIndex = 0; + + for (int i = start; i < end; i++) + { + byte[] wordBytes = BitConverter.GetBytes(microcodeRAM[i]); + wordBytes.CopyTo(microcodeBytes, microcodeIndex); + microcodeIndex += 8; + } + + MD5 md5 = MD5.Create(); + return md5.ComputeHash(microcodeBytes); + } + + private List _mapEntries; + } +} diff --git a/D/Debugger/SourceDisplay.cs b/D/Debugger/SourceDisplay.cs new file mode 100644 index 0000000..dd332a3 --- /dev/null +++ b/D/Debugger/SourceDisplay.cs @@ -0,0 +1,435 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Windows.Forms; + +namespace D.Debugger +{ + /// + /// Presents a source code view aligned with symbol addresses (where applicable) + /// and breakpoints. One source file is loaded into a SourceDisplay at a time. + /// + /// SourceDisplay provides a three-column view: + /// + /// | Breakpoint | Address | Source text | + /// ------------------------------------------------------------------------- + /// + /// Breakpoint and Address are editable (to allow setting breakpoints and to + /// allow modifying symbol table data) + /// + /// + public class SourceDisplay : DataGridView + { + public SourceDisplay() + { + this.VirtualMode = true; + this.RowHeadersVisible = false; + + AddCheckboxColumn("B", DataGridViewAutoSizeColumnMode.ColumnHeader); + AddColumn("Address", false, DataGridViewAutoSizeColumnMode.ColumnHeader); + AddColumn("Source", true, DataGridViewAutoSizeColumnMode.Fill); + } + + public string CurrentSourceFile + { + get { return _currentSourceFile; } + } + + /// + /// Returns the address of the selected line, if any has been assigned. Returns + /// -1 if there is no selection or if no address is available for the selected line. + /// + public int SelectedAddress + { + get + { + int address = -1; + + if (_sourceMap != null && + _currentSourceFile != null && + this.SelectedCells.Count > 0) + { + int rowIndex = this.SelectedCells[0].RowIndex; + // TODO: factor this logic out w/cell input logic. + string cellValue = (string)this.Rows[rowIndex].Cells[1].Value; + + try + { + // strip leading $ if any. + if (cellValue.StartsWith("$")) + { + cellValue = cellValue.Substring(1); + } + + address = Convert.ToUInt16(cellValue, 16); + } + catch + { + address = -1; + } + } + + return address; + } + } + + public SourceMap SourceMap + { + get { return _sourceMap; } + } + + public void SetSourceRoot(string sourceRoot) + { + _sourceRoot = sourceRoot; + } + + public void AttachMap(SourceMap sourceMap) + { + _sourceMap = sourceMap; + } + + /// + /// Loads the appropriate source file, brings the specified line into view + /// and highlights the specified line. + /// + /// + public void SelectSourceEntry(SourceEntry entry, bool readOnly, bool iop) + { + LoadSourceFile(entry.SourcePath); + SelectLine(entry.LineNumber); + + this.ReadOnly = readOnly; + _iopCode = iop; + } + + protected override void OnCurrentCellDirtyStateChanged(EventArgs e) + { + if (IsCurrentCellDirty) + { + // + // Force checkbox changes to commit immediately (rather than + // the default, which is to commit them when focus leaves the cell, which + // is really annoying. + // + if (CurrentCell is DataGridViewCheckBoxCell) + { + CommitEdit(DataGridViewDataErrorContexts.Commit); + } + } + + base.OnCurrentCellDirtyStateChanged(e); + } + + protected override void OnCellValueChanged(DataGridViewCellEventArgs e) + { + if (e.RowIndex < 0 || e.RowIndex > _source.Count) + { + base.OnCellValueChanged(e); + return; + } + + switch (e.ColumnIndex) + { + case 0: // breakpoint + { + // grab the check value + bool cellValue = (bool)this.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue; + + ushort address = 0; + bool addressAvailable = _sourceMap != null ? _sourceMap.GetAddressForSource(new SourceEntry(_currentSourceFile, new string[] { }, 0, e.RowIndex), out address) : false; + + if (addressAvailable) + { + if (cellValue) + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(_iopCode ? BreakpointProcessor.IOP : BreakpointProcessor.CP, BreakpointType.Execution, address)); + } + else + { + BreakpointManager.SetBreakpoint(new BreakpointEntry(_iopCode ? BreakpointProcessor.IOP : BreakpointProcessor.CP, BreakpointType.None, address)); + } + } + } + break; + + case 1: // address + { + string oldCellValue = ((string)this.Rows[e.RowIndex].Cells[e.ColumnIndex].Value).Trim(); + + string[] symbolTokens = ((string)this.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue).Trim().Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + if (symbolTokens.Length > 2) + { + MessageBox.Show("Invalid syntax."); + return; + } + + if (oldCellValue.StartsWith("$")) + { + oldCellValue = oldCellValue.Substring(1); + } + + if (symbolTokens.Length == 0) + { + // cell is empty, delete the current source map entry if present. + // TODO: use source map for this instead? + + if (_sourceMap != null && !string.IsNullOrEmpty(oldCellValue)) + { + ushort oldAddress = Convert.ToUInt16(oldCellValue, 16); + _sourceMap.RemoveSourceEntry(new SourceEntry(_currentSourceFile, new string[] { }, oldAddress, e.RowIndex)); + } + return; + } + + // + // Valid new cell value. + // + string cellValue = symbolTokens[0]; + string symbolName = symbolTokens.Length == 2 ? symbolTokens[1] : "*none*"; + + // strip leading $ if any. + if (cellValue.StartsWith("$")) + { + cellValue = cellValue.Substring(1); + } + + try + { + ushort address = Convert.ToUInt16(cellValue, 16); + ushort oldAddress = string.IsNullOrWhiteSpace(oldCellValue) ? (ushort)0 : Convert.ToUInt16(oldCellValue, 16); + + if (_sourceMap != null && address != oldAddress) + { + // + // Set the new value first. + // + _sourceMap.AddSourceEntry(new SourceEntry(_currentSourceFile, new string[] { symbolName }, address, e.RowIndex)); + + // + // Remove the old value from the database if there is one. + // + if (!string.IsNullOrWhiteSpace(oldCellValue)) + { + _sourceMap.RemoveSourceEntry(new SourceEntry(_currentSourceFile, new string[] { }, oldAddress, e.RowIndex)); + } + } + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, "Hey!"); + // Invalid value, clear it. + // this.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = String.Empty; + } + } + break; + } + + + base.OnCellValueChanged(e); + } + + protected override void OnCellValueNeeded(DataGridViewCellValueEventArgs e) + { + if (e.RowIndex > _source.Count) + { + // Past end of source, nothing to do. + return; + } + + switch (e.ColumnIndex) + { + case 0: + { + ushort address = 0; + bool addressAvailable = _sourceMap != null ? _sourceMap.GetAddressForSource(new SourceEntry(_currentSourceFile, new string[] { }, 0, e.RowIndex), out address) : false; + if (addressAvailable) + { + e.Value = BreakpointManager.GetBreakpoint(_iopCode ? BreakpointProcessor.IOP : BreakpointProcessor.CP, address) != BreakpointType.None; + } + else + { + e.Value = false; // TODO: hook to breakpoint system + } + } + break; + + case 1: + { + ushort address = 0; + bool addressAvailable = _sourceMap != null ? _sourceMap.GetAddressForSource(new SourceEntry(_currentSourceFile, new string[] { }, 0, e.RowIndex), out address) : false; + e.Value = addressAvailable ? String.Format("${0:x4}", address) : String.Empty; + } + break; + + case 2: + e.Value = _source[e.RowIndex]; + break; + + default: + throw new InvalidOperationException("Unhandled column."); + } + + base.OnCellValueNeeded(e); + } + + private void LoadSourceFile(string sourcePath) + { + // + // Only do this if the file isn't currently loaded. + // + try + { + if (_currentSourceFile != sourcePath) + { + this.Invalidate(); + + _source = new List(); + + using (StreamReader sr = new StreamReader(Path.Combine(_sourceRoot, sourcePath), Encoding.UTF8)) + { + while (!sr.EndOfStream) + { + _source.Add(UnTabify(sr.ReadLine())); + } + } + + this.RowCount = _source.Count; + + _currentSourceFile = sourcePath; + } + } + catch(Exception e) + { + _source = new List(); + _source.Add( + String.Format("Unable to load source file {0}. Error: {1}", + sourcePath, e.Message)); + } + } + + /// + /// Converts tabs in the given string to 4 space tabulation. As it should be. + /// + /// + /// + private string UnTabify(string tabified) + { + StringBuilder untabified = new StringBuilder(); + + int column = 0; + + foreach(char c in tabified) + { + if (c == '\t') + { + untabified.Append(" "); + column++; + while ((column % 4) != 0) + { + untabified.Append(" "); + column++; + } + } + if (c == _unicodeUnknown) + { + // TODO: + // We assume that if this happens it's the microcode source "arrow" symbol. + // C#'s StreamReader supports only Unicode/UTF and ASCII (7-bit) encodings and the + // Star's backarrow is an 8-bit character. I should really just rewrite the code + // to read the bytes in myself, this is a bodge for the time being. + untabified.Append(_arrowChar); + } + else + { + untabified.Append(c); + column++; + } + } + + return untabified.ToString(); + } + + private void SelectLine(int lineNumber) + { + // + // Clear the current selection and move the selection to the first cell + // of the requested line. + // + + if (lineNumber < this.Rows.Count) + { + this.Rows[lineNumber].Selected = true; + this.CurrentCell = this.Rows[lineNumber].Cells[0]; + } + else + { + this.ClearSelection(); + } + } + + private void AddColumn(string name, bool readOnly, DataGridViewAutoSizeColumnMode sizeMode) + { + int index = this.Columns.Add(name, name); + + this.Columns[index].ReadOnly = readOnly; + this.Columns[index].Resizable = DataGridViewTriState.False; + this.Columns[index].SortMode = DataGridViewColumnSortMode.NotSortable; + this.Columns[index].AutoSizeMode = sizeMode; + } + + private void AddCheckboxColumn(string name, DataGridViewAutoSizeColumnMode sizeMode) + { + int index = this.Columns.Add(new DataGridViewCheckBoxColumn()); + + this.Columns[index].HeaderText = name; + this.Columns[index].ReadOnly = false; + this.Columns[index].Resizable = DataGridViewTriState.False; + this.Columns[index].SortMode = DataGridViewColumnSortMode.NotSortable; + this.Columns[index].AutoSizeMode = sizeMode; + } + + private string _currentSourceFile; + private string _sourceRoot; + private List _source; + private bool _iopCode; + private SourceMap _sourceMap; + + // + // Character substitutions + // + private const char _arrowChar = '←'; + private const char _unicodeUnknown = (char)0xfffd; + + } +} diff --git a/D/Debugger/SourceMap.cs b/D/Debugger/SourceMap.cs new file mode 100644 index 0000000..a236fa4 --- /dev/null +++ b/D/Debugger/SourceMap.cs @@ -0,0 +1,442 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace D.Debugger +{ + public class SourceEntry + { + public SourceEntry(string sourcePath, string[] symbolNames, ushort address, int lineNumber) + { + SourcePath = sourcePath; + SymbolNames = symbolNames; + Address = address; + LineNumber = lineNumber; + } + + public override string ToString() + { + return String.Format("{0}, line {1} address 0x{2:x4}", SourcePath, LineNumber, Address); + } + + public string SourcePath; + public string[] SymbolNames; + public ushort Address; + public int LineNumber; + + public static readonly SourceEntry Empty = new SourceEntry(String.Empty, new string[] { "*none*" }, 0, 0); + } + + public class SourceMap + { + public SourceMap(string mapName, string mapFile, string sourceRoot) + { + _sourceFileToSourceEntryMap = new Dictionary>(); + _orderedSourceEntries = new List(); + + ReadMap(mapFile, sourceRoot); + + _mapName = mapName; + _mapFile = mapFile; + _sourceRoot = sourceRoot; + } + + public string MapName + { + get { return _mapName; } + } + + public string SourceRoot + { + get { return _sourceRoot; } + } + + /// + /// Returns the list of source files referenced by this map. + /// + /// + public List GetSourceFiles() + { + return _sourceFileToSourceEntryMap.Keys.ToList(); + } + + public void Save() + { + string header = + @"# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present."; + + using (StreamWriter sw = new StreamWriter(_mapFile)) + { + // Write out a nice header + sw.Write(header); + + sw.WriteLine(); + + // Write out each source file + foreach(string sourceFile in _sourceFileToSourceEntryMap.Keys) + { + sw.WriteLine("[{0}]", sourceFile); + + foreach(SourceEntry entry in _sourceFileToSourceEntryMap[sourceFile]) + { + StringBuilder symbolList = new StringBuilder(); + for (int i = 0; i < entry.SymbolNames.Length; i++) + { + symbolList.AppendFormat(i < entry.SymbolNames.Length - 1 ? "{0}," : "{0}", entry.SymbolNames[i]); + } + + sw.WriteLine("{0}: 0x{1:x4},{2}", symbolList.ToString(), entry.Address, entry.LineNumber + 1); // line numbers are 1-indexed + } + + sw.WriteLine(); + } + } + } + + public SourceEntry GetSourceForAddress(ushort address) + { + SourceEntry result = null; + + // + // Find the SourceEntry nearest this address, if there is one. + // + foreach(SourceEntry entry in _orderedSourceEntries) + { + if (entry.Address > address) + { + break; + } + + result = entry; + } + + return result; + } + + public SourceEntry GetExactSourceForAddress(ushort address) + { + // + // Find the SourceEntry for this address, if there is one. + // + foreach (SourceEntry entry in _orderedSourceEntries) + { + if (entry.Address == address) + { + return entry; + } + } + + return null; + } + + public SourceEntry GetNearestSymbolForAddress(ushort address) + { + SourceEntry result = null; + + // + // Find the SourceEntry nearest this address that has a symbol name defined, if there is one. + // + foreach (SourceEntry entry in _orderedSourceEntries) + { + if (entry.Address > address) + { + break; + } + + if (entry.SymbolNames.Length > 0 && + entry.SymbolNames[0] != "*none*") // TODO: move to constant + { + result = entry; + } + } + + return result; + } + + public bool GetAddressForSource(SourceEntry entry, out ushort address) + { + bool found = false; + address = 0; + + // + // Find the source line entry that matches. + // + if (_sourceFileToSourceEntryMap.ContainsKey(entry.SourcePath)) + { + foreach(SourceEntry line in _sourceFileToSourceEntryMap[entry.SourcePath]) + { + if (line.LineNumber == entry.LineNumber) + { + found = true; + address = line.Address; + break; + } + } + } + + return found; + } + + public void AddSourceEntry(SourceEntry entry) + { + // InsertAddressEntry will ensure that no duplicate addresses are added. + InsertAddressEntry(entry); + InsertSourceEntry(entry); + } + + public void AddSourceFile(string sourcePath) + { + if (!_sourceFileToSourceEntryMap.ContainsKey(sourcePath)) + { + List newList = new List(); + _sourceFileToSourceEntryMap.Add(sourcePath, newList); + } + else + { + throw new InvalidOperationException("Source file already exists in map."); + } + } + + public void RemoveSourceEntry(SourceEntry entry) + { + // Remove from address table + for (int i = 0; i < _orderedSourceEntries.Count; i++) + { + if (_orderedSourceEntries[i].Address == entry.Address) + { + _orderedSourceEntries.RemoveAt(i); + break; + } + } + + // Remove from source table if present + if (_sourceFileToSourceEntryMap.ContainsKey(entry.SourcePath)) + { + for (int i = 0; i < _sourceFileToSourceEntryMap[entry.SourcePath].Count; i++) + { + if (_sourceFileToSourceEntryMap[entry.SourcePath][i].Address == entry.Address) + { + _sourceFileToSourceEntryMap[entry.SourcePath].RemoveAt(i); + break; + } + } + } + + } + + private void ReadMap(string mapFile, string sourceRoot) + { + // + // If the file does not exist, we will just start with an empty map. + // + if (!File.Exists(mapFile)) + { + return; + } + + using (StreamReader map = new StreamReader(mapFile)) + { + string sourceFile = string.Empty; + ReadState state = ReadState.NextFileHeader; + int mapLineNumber = 0; + + while (!map.EndOfStream) + { + string line = map.ReadLine().Trim(); + mapLineNumber++; + + if (string.IsNullOrWhiteSpace(line) || + line.StartsWith("#")) + { + // Nothing of note here, continue. + continue; + } + + if (line.StartsWith("[")) + { + // Looks like the start of a file header. + state = ReadState.NextFileHeader; + } + + switch(state) + { + case ReadState.NextFileHeader: + // We expect the line to be in the form "[]" + // If this is not the case, then the map file is incorrectly formed. + if (line.StartsWith("[")) + { + int closingBracket = line.LastIndexOf(']'); + + if (closingBracket < 0) + { + throw new InvalidOperationException( + String.Format("Badly formed source file entry on line {0}", mapLineNumber)); + } + + sourceFile = line.Substring(1, closingBracket - 1).Trim(); + state = ReadState.NextSymbolEntry; + } + else + { + throw new InvalidOperationException( + String.Format("Expected file header on line {0}", mapLineNumber)); + } + break; + + case ReadState.NextSymbolEntry: + // + // This is expected to be a symbol map entry, which looks like + // , .. , :
, + // + string[] symbolAddressTokens = line.Split(':'); + + if (symbolAddressTokens.Length != 2) + { + // Should be two tokens here, one on each side of the ':' + throw new InvalidOperationException( + String.Format("Badly formed symbol entry on line {0}", mapLineNumber)); + } + + // Grab the symbol names. There must be at least one present since the above split succeeded. + string[] symbolNames = symbolAddressTokens[0].Trim().Split(','); + + // Grab the source information. There must be exactly two entries. + string[] sourceData = symbolAddressTokens[1].Trim().Split(','); + + if (sourceData.Length != 2) + { + throw new InvalidOperationException( + String.Format("Badly formed symbol entry on line {0} -- source information is invalid.", mapLineNumber)); + } + + // Convert source info into integers and build a new SourceEntry. + int lineNumber = 0; + ushort address = 0; + try + { + address = (ushort)Convert.ToInt32(sourceData[0].Trim(), 16); + lineNumber = int.Parse(sourceData[1].Trim()) - 1; // line numbers are 1-indexed + } + catch(Exception) + { + throw new InvalidOperationException( + String.Format("Badly formed symbol entry on line {0} -- source information is invalid.", mapLineNumber)); + } + + SourceEntry newEntry = new SourceEntry(sourceFile, symbolNames, address, lineNumber); + + AddSourceEntry(newEntry); + break; + } + } + } + } + + private void InsertAddressEntry(SourceEntry entry) + { + if (_orderedSourceEntries.Count == 0) + { + _orderedSourceEntries.Add(entry); + return; + } + + // Find the first entry that has an address greater than the new entry's. + // + for(int i=0;i<_orderedSourceEntries.Count;i++) + { + // Sanity check -- if these entries have equal addresses then we need to stop here. + if (_orderedSourceEntries[i].Address == entry.Address) + { + throw new InvalidOperationException( + String.Format("Duplicate address {0:x4} in source map.", entry.Address)); + } + + if (_orderedSourceEntries[i].Address > entry.Address) + { + _orderedSourceEntries.Insert(i, entry); + return; + } + } + + // + // If we get here, then this address is greater than any already in the list, so we add it at the end. + // + _orderedSourceEntries.Add(entry); + } + + private void InsertSourceEntry(SourceEntry entry) + { + if (!_sourceFileToSourceEntryMap.ContainsKey(entry.SourcePath)) + { + List newList = new List(); + newList.Add(entry); + _sourceFileToSourceEntryMap.Add(entry.SourcePath, newList); + } + else + { + _sourceFileToSourceEntryMap[entry.SourcePath].Add(entry); + } + } + + // + // Maps for quick lookups + // + private Dictionary> _sourceFileToSourceEntryMap; + + // + // Ordered list for quick search by address + // + private List _orderedSourceEntries; + + private string _mapName; + private string _mapFile; + private string _sourceRoot; + + enum ReadState + { + NextFileHeader, + NextSymbolEntry, + } + } +} diff --git a/D/Disks/130P26300-diags.IMD b/D/Disks/130P26300-diags.IMD new file mode 100644 index 0000000..4eae04c Binary files /dev/null and b/D/Disks/130P26300-diags.IMD differ diff --git a/D/Disks/130P26301-install.IMD b/D/Disks/130P26301-install.IMD new file mode 100644 index 0000000..db78952 Binary files /dev/null and b/D/Disks/130P26301-install.IMD differ diff --git a/D/Disks/Harmony.img b/D/Disks/Harmony.img new file mode 100644 index 0000000..b05fb42 Binary files /dev/null and b/D/Disks/Harmony.img differ diff --git a/D/Disks/Koto.img b/D/Disks/Koto.img new file mode 100644 index 0000000..842a765 Binary files /dev/null and b/D/Disks/Koto.img differ diff --git a/D/Disks/Lyric.img b/D/Disks/Lyric.img new file mode 100644 index 0000000..58f61dc Binary files /dev/null and b/D/Disks/Lyric.img differ diff --git a/D/Disks/Medley.img b/D/Disks/Medley.img new file mode 100644 index 0000000..f4b3f16 Binary files /dev/null and b/D/Disks/Medley.img differ diff --git a/D/Disks/ViewPoint-11-9-1990-18-38.img b/D/Disks/ViewPoint-11-9-1990-18-38.img new file mode 100644 index 0000000..a0a58cf Binary files /dev/null and b/D/Disks/ViewPoint-11-9-1990-18-38.img differ diff --git a/D/Disks/XDE.img b/D/Disks/XDE.img new file mode 100644 index 0000000..ad9c27a Binary files /dev/null and b/D/Disks/XDE.img differ diff --git a/D/Disks/XDE_5.0_BO1.IMD b/D/Disks/XDE_5.0_BO1.IMD new file mode 100644 index 0000000..859e4c1 Binary files /dev/null and b/D/Disks/XDE_5.0_BO1.IMD differ diff --git a/D/Display/DisplayController.cs b/D/Display/DisplayController.cs new file mode 100644 index 0000000..0ec5f9c --- /dev/null +++ b/D/Display/DisplayController.cs @@ -0,0 +1,295 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using D.Logging; +using System.Collections.Generic; + +namespace D.Display +{ + public class DisplayController + { + public DisplayController(DSystem system) + { + _system = system; + + _lostSyncEvent = new Event(_lostSyncInterval, null, LostSyncCallback); + + _fifo = new Queue(16); + + } + + public void Reset() + { + _displayOn = false; + _blank = false; + _picture = false; + _invert = false; + _oddLine = false; + + _scanline = 0; + + _fifo.Clear(); + + if (_system.Display != null) + { + _system.Display.Clear(); + } + } + + public bool DisplayOn + { + get { return _displayOn; } + } + + public void ClrDpRq() + { + // + // Put the display task to sleep + // + _system.CP.SleepTask(TaskType.Display); + } + + public void SetDCtlFifo(ushort value) + { + // if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.DisplayControl, "DCtlFIFO<-0x{0:x4}", value); + if (_fifo.Count < 16) + { + _fifo.Enqueue(value); + } + else + { + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.DisplayControl, "DCtlFIFO: FIFO overflow, word dropped."); + } + } + + public void SetDCtl(ushort value) + { + bool displayOn = _displayOn; + _displayOn = (value & 0x01) != 0; + _blank = (value & 0x02) != 0; + _picture = (value & 0x04) != 0; + _invert = (value & 0x08) != 0; + + if ((value & 0x20) != 0) + { + // Vertical Sync -- back to the top of the screen + _scanline = 0; + _oddLine = (value & 0x10) != 0; + _syncPresent = true; + _system.Display.Render(); + } + + if ((value & 0x40) == 0) + { + // Clear control fifo + _fifo.Clear(); + } + + if (!displayOn && _displayOn) + { + // Kick off the horizontal retrace callback since we're turning the display on. + _system.Scheduler.Schedule(_horizontalRetraceDelay, HorizontalRetraceCallback); + + _system.Scheduler.Cancel(_lostSyncEvent); + _lostSyncEvent = _system.Scheduler.Schedule(_lostSyncInterval, LostSyncCallback); + } + else if (!_displayOn) + { + // + // Put the display task to sleep. + // + _system.CP.SleepTask(TaskType.Display); + } + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.DisplayControl, "DCtl<-0x{0:x4}: On={1} Blank={2} Picture={3} Invert={4} Odd={5}" + , value, + _displayOn, + _blank, + _picture, + _invert, + _oddLine); + } + + public void SetDBorder(ushort value) + { + _displayBorder = value; + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.DisplayControl, "DBorder<-0x{0:x4}", value); + } + + /// + /// Invoked at the end of every scanline: Wake display task, update state, schedule next callback + /// as necessary. + /// + /// + /// + private void HorizontalRetraceCallback(ulong skewNsec, object context) + { + int visibleOffset = _oddLine ? 37 : 36; + int effectiveScanline = _scanline - visibleOffset; + + // + // Render this scanline, if there's anything to do. + // + if (_blank) + { + // Render blank scanline (no border, no picture) + for (int i = 0; i < _scanlineData.Length; i++) + { + _scanlineData[i] = 0; + } + } + else + { + if (_picture) + { + // Normal line : 32 bits of border pattern, 1024 bits of display, 32 bits of border pattern + // Border pattern: low byte on lines 4n, 4n+1; high byte on 4n+2, 4n+3. + int patternByte = (effectiveScanline & 0x2) == 0 ? _displayBorder & 0xff : _displayBorder >> 8; + ushort patternWord = (ushort)(patternByte | (patternByte << 8)); + + _scanlineData[0] = patternWord; + _scanlineData[1] = patternWord; + + if (_fifo.Count > 0) + { + // Grab first segment from FIFO + ushort fifoWord = _fifo.Dequeue(); + int lastWord = fifoWord >> 10; + int lineNumber = fifoWord & 0x3ff; + bool valid = false; + for (int word = 0; word < 64; word++) + { + _scanlineData[word + 2] = _system.MemoryController.DebugMemory.ReadWord((lineNumber << 6) | word, out valid); + + // Grab next segment if this isn't the last word in the scanline. + if (word != 63 && word == lastWord && _fifo.Count > 0) + { + fifoWord = _fifo.Dequeue(); + lastWord = fifoWord >> 10; + lineNumber = fifoWord & 0x3ff; + } + } + } + else + { + // Blank out display words, nothing in the FIFO. + for (int i = 2; i < 64; i++) + { + _scanlineData[i] = 0; + } + } + + _scanlineData[66] = patternWord; + _scanlineData[67] = patternWord; + } + else + { + // Just display the border pattern everywhere: + // low byte on lines 4n, 4n+1; high byte on 4n+2, 4n+3. + int patternByte = (effectiveScanline & 0x2) == 0 ? _displayBorder & 0xff : _displayBorder >> 8; + ushort patternWord = (ushort)(patternByte | (patternByte << 8)); + + for (int i = 0; i < _scanlineData.Length; i++) + { + _scanlineData[i] = patternWord; + } + } + } + + if (effectiveScanline > 0 && effectiveScanline < 860) + { + // Render to screen + _system.Display.DrawScanline(effectiveScanline, _scanlineData, _invert); + } + + // Move to next scanline + _scanline += 2; + + // + // Schedule next retrace as long as the display is still on. + // + if (_displayOn) + { + _system.Scheduler.Schedule(_horizontalRetraceDelay, HorizontalRetraceCallback); + + // + // End of scanline: Wake up the display task. + // + _system.CP.WakeTask(TaskType.Display); + } + } + + private void LostSyncCallback(ulong skewNsec, object context) + { + if (_syncPresent) + { + // + // Got sync, keep the display alive and reschedule ourselves. + _lostSyncEvent = _system.Scheduler.Schedule(_lostSyncInterval, LostSyncCallback); + } + else + { + // No sync since the last callback, blank the display and stop the sync callback. + _system.Display.Clear(); + } + + _syncPresent = false; + } + + // Control bits + private bool _displayOn; + private bool _blank; + private bool _picture; + private bool _invert; + private bool _oddLine; + + // Border bitmap + private ushort _displayBorder; + + // Control FIFO. Max 16 entries. + private Queue _fifo; + + // Scanline + private int _scanline; + private ushort[] _scanlineData = new ushort[64 + 4]; // 1024 bits picture, 32 bits border on either side + + private bool _syncPresent; + + private DSystem _system; + + // + // Timing and events + // + private readonly ulong _horizontalRetraceDelay = (ulong)(28.8 * Conversion.UsecToNsec); // 28.8uS + + private Event _lostSyncEvent; + private readonly ulong _lostSyncInterval = (ulong)(52.91 * Conversion.MsecToNsec); // 53ms (one frame time) + } +} diff --git a/D/Ethernet/CRC32.cs b/D/Ethernet/CRC32.cs new file mode 100644 index 0000000..687997b --- /dev/null +++ b/D/Ethernet/CRC32.cs @@ -0,0 +1,94 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.Ethernet +{ + public class CRC32 + { + static CRC32() + { + // + // Initialize the CRC table. + // + uint temp = 0; + for (uint i = 0; i < _crcTable.Length; i++) + { + temp = i; + + for (int j = 8; j > 0; j--) + { + if ((temp & 1) == 1) + { + temp = (uint)((temp >> 1) ^ _polynomial); + } + else + { + temp >>= 1; + } + } + + _crcTable[i] = temp; + } + } + + public CRC32() + { + Reset(); + } + + public uint Checksum + { + get { return ~_checksum; } + } + + public void Reset() + { + _checksum = 0xffffffff; + } + + public void AddToChecksum(ushort word) + { + byte[] bytes = new byte[2]; + bytes[0] = (byte)(word >> 8); + bytes[1] = (byte)word; + + for (int i = 0; i < bytes.Length; i++) + { + byte index = (byte)((_checksum ^ bytes[i]) & 0xff); + _checksum = (_checksum >> 8) ^ _crcTable[index]; + } + } + + private uint _checksum; + + private static uint[] _crcTable = new uint[256]; + private const uint _polynomial = 0xedb88320; + } + +} diff --git a/D/Ethernet/EthernetController.cs b/D/Ethernet/EthernetController.cs new file mode 100644 index 0000000..452de85 --- /dev/null +++ b/D/Ethernet/EthernetController.cs @@ -0,0 +1,849 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using D.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace D.Ethernet +{ + /// + /// EthernetController implements the Star's Ethernet controller. + /// At this time, no official documentation exists; only microcode listings and the schematic drawings. + /// The code is implemented based on study of the schematics and diagnostic microcode. + /// As such there is a lot of handwavy stuff in here, especially around loopback. At this time enough + /// is implemented to get boot diagnostics to pass. + /// + /// Questions remaining to be answered: + /// - What is the distinction between Loopback and LocalLoopback? Initially it looked like LocalLoopback only + /// looped back through the FIFO, but it appears to invoke CRC generation and microcode comments make it look + /// like transmission actually takes place? At this time both are treated identically. + /// - How is the FIFO actually controlled during loopback? (How does the transmit hardware know when to stop + /// transmitting when the FIFO is (apparently) being used for both transmit and receive at the same time during + /// a loopback operation?) + /// + /// - Why is CRC calculation not working the way I expect? The residual sum does not appear to work out to + /// the expected value for Ethernet CRC32. + /// + /// + public class EthernetController + { + public EthernetController(DSystem system) + { + _system = system; + _fifo = new Queue(); + _inputPacket = new Queue(); + _outputPacket = new Queue(); + _pendingPackets = new Queue(); + + _crc32 = new CRC32(); + + // Attach real Ethernet device if user has specified one, otherwise leave unattached; output data + // will go into a bit-bucket. + try + { + + if (Configuration.HostRawEthernetInterfacesAvailable && + !string.IsNullOrWhiteSpace(Configuration.HostPacketInterfaceName)) + { + _hostInterface = new HostEthernetEncapsulation(Configuration.HostPacketInterfaceName); + _hostInterface.RegisterReceiveCallback(OnHostPacketReceived); + } + } + catch (Exception e) + { + _hostInterface = null; + Log.Write(LogComponent.HostEthernet, "Unable to configure network interface. Error {0}", e.Message); + } + + _readerLock = new ReaderWriterLockSlim(); + + // Start the ethernet reciever poll event, this will run forever. + _system.Scheduler.Schedule(_receiverPollInterval, ReceiverPollCallback); + + Reset(); + } + + public void Reset() + { + _turnOff_ = false; + _rxEvenLen = false; + _rxGoodCRC = false; + _rxOverrun_ = true; + _txUnderrun = false; + _txCollision_ = true; + _rxMode_ = true; + _enableTx = false; + _lastWord = false; + _enableRcv = false; + _localLoop = false; + _loopBack = false; + _defer = false; + + _purge = false; + _tickElapsed = false; + _outAttn = false; + _inAttn = false; + _transmitterRunning = false; + + _outputData = 0; + _outputDataLatched = false; + _fifo.Clear(); + _inputPacket.Clear(); + _crc32.Reset(); + } + + public int EtherDisp() + { + // + // Pin 139(YIODisp.1) : Hooked to "Attn," which appear to be whether any attention is needed by the receiver + // or transmitter. + // Pin 39(YIODisp.0) : "(schematic) Must be zero for the transmitting inner loop uCode. It is also used to + // determine if the Option card is plugged in." + // + int value = 0; + + if (_turnOff_) + { + // + // Ethernet is not turned off, returned value is based on whether + // the transmitter or reciever hardware has a status to report. + value = _outAttn | _inAttn ? 1 : 0; + } + + return value; + } + + public ushort EStatus() + { + ushort value = (ushort) + ~((_turnOff_ ? 0x0001 : 0x00) | + (_rxEvenLen ? 0x0002 : 0x00) | + (_rxGoodCRC ? 0x0004 : 0x00) | + (_rxOverrun_ ? 0x0008 : 0x00) | + (_rxGoodAlign ? 0x0010 : 0x00) | + (!_txUnderrun ? 0x0020 : 0x00) | + (_txCollision_ ? 0x0040 : 0x00) | + (_rxMode_ ? 0x0080 : 0x00) | + (_enableTx ? 0x0100 : 0x00) | + (_lastWord ? 0x0200 : 0x00) | + (_enableRcv ? 0x0400 : 0x00) | + (_localLoop ? 0x0800 : 0x00) | + (_loopBack ? 0x1000 : 0x00)); + + return value; + } + + public void EOCtl(ushort value) + { + // EOCtl: Bit(etc) + // ---------------------------- + // EnableTrn 15 + // LastWord 14 + // Defer 13 + _enableTx = (value & 0x1) != 0; + _lastWord = (value & 0x2) != 0; + _defer = (value & 0x4) != 0; + + _outAttn = false; + _txUnderrun = false; + _txCollision_ = true; + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, + "EOCtl<- 0x{0:x4}: enableTx {1} lastWord {2} defer {3}", + value, + _enableTx, + _lastWord, + _defer + ); + + // + // Writing EOCtl resets the defer clock + // + _tickElapsed = false; + _system.Scheduler.Cancel(_deferEvent); + + if (_defer) + { + // + // Queue up an event 51.2uS in the future, this will + // set _tickElapsed, update wakeups, and start the transmitter when it fires. + // + _deferEvent = _system.Scheduler.Schedule(_deferDelay, DeferCallback); + } + else + { + // + // Start the transmitter running if need be (if it isn't already). + // + if (_enableTx && !_transmitterRunning && !_lastWord) + { + _crc32.Reset(); + StartTransmitter(); + } + } + + if (!_enableTx) + { + _fifo.Clear(); + StopTransmitter(); + } + + UpdateWakeup(); + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EOCtl end."); + } + + public void EICtl(ushort value) + { + // EICtl: Bit(xerox order) + // ------------------------------------ + // EnableRcv 15 + // TurnOff' 14 + // LocalLoop 13 + // LoopBack 12 + _enableRcv = (value & 0x1) != 0; + _turnOff_ = (value & 0x2) == 0; + _localLoop = (value & 0x4) != 0; + _loopBack = (value & 0x8) != 0; + + if (!_enableRcv) + { + // Reset receive state + _rxMode_ = true; + _receiverState = ReceiverState.Preamble; + _inAttn = false; + + _rxGoodCRC = true; + _rxGoodAlign = true; + _rxOverrun_ = true; + _rxEvenLen = true; + + if (!_loopBack) + { + _fifo.Clear(); + } + + _inputPacket.Clear(); + _crc32.Reset(); + } + + UpdateWakeup(); + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EICtl<- 0x{0:x4} enablerx {1} turnOff' {2} localLoop {3} loopBack {4}.", + value, + _enableRcv, + _turnOff_, + _localLoop, + _loopBack); + } + + public void EOData(ushort value) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EOData<- 0x{0:x4}.", value); + + _outputData = value; + _outputDataLatched = true; + } + + public void EStrobe(int cycle) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EStrobe."); + + if ((cycle == 1 || cycle == 3) & !_lastWord) + { + // Strobe output data into FIFO. + + if (!_outputDataLatched) + { + // This is not actually an underrun case, it indicates a case where the microcode is doing + // something we do not expect. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.EthernetControl, "EStrobe: no data latched."); + _outAttn = false; + } + + // + // Move data from output data word into the FIFO. + // + if (_fifo.Count < 16) + { + _fifo.Enqueue(_outputData); + _outputDataLatched = false; + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EStrobe: loaded word 0x{0:x4} into FIFO. FIFO count is now {1}", + _outputData, _fifo.Count); + + _outAttn = false; + + UpdateWakeup(); + } + else + { + _fifo.Dequeue(); + _fifo.Enqueue(_outputData); + // This should not happen; microcode should sleep when the FIFO is full. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.EthernetControl, "EStrobe: FIFO full, dropping word."); + } + } + else if (cycle == 2) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "EStrobe: (cycle 2) flushing received data."); + + // Throw out input data and stop the receiver + _fifo.Clear(); + _inAttn = false; + _rxMode_ = true; + StopReceiver(); + UpdateWakeup(); + } + } + + public ushort EIData(int cycle) + { + ushort value = 0; + + // + // Read from the input/output FIFO. + // + if (_fifo.Count > 0) + { + value = _fifo.Dequeue(); + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "<-EIData: Returning FIFO word 0x{0:x4}. FIFO count is now {1}", + value, _fifo.Count); + } + else + { + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.EthernetControl, "<-EIData: FIFO empty."); + + // TODO: does this cause an underrun? + } + + return value; + } + + private void DeferCallback(ulong skewNsec, object context) + { + _tickElapsed = true; + UpdateWakeup(); + _tickElapsed = false; + + // + // Start the transmitter. + // + if (_enableTx && !_transmitterRunning) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Defer complete, starting transmitter."); + StartTransmitter(); + } + } + + private void StartTransmitter() + { + // + // Start transmit clock running; this will wake up every + // 1600ns to pick up a word (if available) from the fifo + // and transmit it. (If Defer is active, we will do this + // after the deferral period has elapsed.) + // + if (!_transmitterRunning) + { + // + // First abort any transmit clock that may be running. + // + _system.Scheduler.Cancel(_transmitEvent); + + // + // Schedule the transmission callback. + _transmitEvent = _system.Scheduler.Schedule(_ipgInterval, TransmitCallback); + + // + // Clear the output packet. + // + _outputPacket.Clear(); + + _transmitterRunning = true; + } + else + { + throw new InvalidOperationException("Transmitter already running."); + } + } + + private void StopTransmitter() + { + _system.Scheduler.Cancel(_transmitEvent); + _transmitterRunning = false; + } + + private void TransmitWord(ushort word) + { + if (_localLoop || _loopBack) + { + // Loop back to FIFO through the receiver. + + // + // Append this word to the input packet. + // It will be picked up by the Receive callback and + // put into the FIFO in due time. + // + _inputPacket.Enqueue(word); + + // + // Ensure the receiver is running. + // + RunReceiver(); + } + else + { + // Append to outgoing packet. + _outputPacket.Enqueue(word); + } + } + + private void CompleteTransmission() + { + // + // Transmit completed packet over real ethernet. + // + + // + // A properly formed packet generated by the microcode should begin with the standard ethernet + // SFD of 3 words of 0x5555 and 1 word of 0x55d5. This must be stripped before we send it + // to the host device. + // + if (_outputPacket.Count < 4) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Malformed packet: too short."); + return; + } + + bool badSfd = false; + for(int i=0;i<4;i++) + { + ushort sfdWord = _outputPacket.Dequeue(); + + if (i < 3) + { + badSfd = sfdWord != 0x5555; + } + else + { + badSfd = sfdWord != 0x55d5; + } + } + + if (badSfd) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Malformed packet: Invalid SFD."); + return; + } + + if (_outputPacket.Count > 0 && _hostInterface != null) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Transmitting completed packet."); + _hostInterface.Send(_outputPacket.ToArray()); + } + } + + private void TransmitCallback(ulong skewNsec, object context) + { + // + // Pull the next word from the FIFO, if available. + // + if (_fifo.Count > 0) + { + ushort nextWord = _fifo.Dequeue(); + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Transmitting word 0x{0:x4}", nextWord); + TransmitWord(nextWord); + } + else if (!_lastWord) + { + // + // No data available in FIFO and LastWord is not set: Underrun. + // Raise txUnderrun to signal an error. + // + _txUnderrun = true; + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.EthernetControl, "Transmit underrun."); + } + + if (_lastWord && _fifo.Count == 0) + { + // + // If LastWord is set and the FIFO is empty, that will be the last word in the packet. Shut things down. + // + _transmitterRunning = false; + _outAttn = true; + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Last word. Stopping transmission."); + + // + // Transmit completed packet over real ethernet. + // + CompleteTransmission(); + } + else if (_txUnderrun) + { + _transmitterRunning = false; + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Underrun. Stopping transmission."); + } + else + { + // + // Still going, schedule the next callback. + // + _transmitEvent = _system.Scheduler.Schedule(_transmitInterval, TransmitCallback); + } + + // + // Update wakeups -- if the FIFO has space now, the microcode should be awakened, for example. + // + UpdateWakeup(); + } + + /// + /// Invoked when the host ethernet interface receives a packet destined for us. + /// NOTE: This runs on the PCap or UDP receiver thread, not the main emulator thread. + /// Any access to emulator structures must be properly protected. + /// + /// + /// + /// + private void OnHostPacketReceived(MemoryStream data) + { + // + // Append the new packet onto our pending packets queue. + // This will be picked up when the receiver is ready to receive things. + // + _readerLock.EnterUpgradeableReadLock(); + if (!_enableRcv || !_turnOff_) + { + // + // Receiver is offjust drop the packet on the floor. + // + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Ethernet receiver is off; dropping this packet."); + + _readerLock.EnterWriteLock(); + _pendingPackets.Clear(); + _readerLock.ExitWriteLock(); + } + else if (_pendingPackets.Count < 32) + { + // + // Place the packet into the queue; this will be picked up by the receiver poll thread + // and passed to the receiver. + // + _readerLock.EnterWriteLock(); + _pendingPackets.Enqueue(data); + _readerLock.ExitWriteLock(); + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Packet (length {0}) added to pending buffer.", data.Length); + } + else + { + // + // Too many queued-up packets, drop this one. + // + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Pending buffer full; dropping this packet."); + } + _readerLock.ExitUpgradeableReadLock(); + } + + private void StopReceiver() + { + _system.Scheduler.Cancel(_receiveEvent); + _receiverRunning = false; + } + + private void RunReceiver() + { + _rxMode_ = false; + + if (!_receiverRunning && _enableRcv) + { + // + // This is a hack: For loopback cases the real hardware has mysterious state machines to deal with + // tracking the FIFO properly (since it's being used for both input and output at the same time, + // something that only occurs during loopback testing -- the complication is how the transmit state machine knows + // when the last word provided by the microcode has been sent, when the loopback is bringing new words into the FIFO + // at the same time). + // Because we live in a fantasy world of emulation, we can cheat: To keep things simple here we simply delay the + // receive operation to ensure that there is no overlap between the transmit and receive on loopback, this avoids + // needing extra logic for the FIFO during loopback tests. + // + _receiveEvent = _system.Scheduler.Schedule( + _localLoop || _loopBack ? _receiveIntervalLoopback : _receiveInterval, + ReceiveCallback); + + _receiverRunning = true; + } + } + + private void ReceiverPollCallback(ulong skewNsec, object context) + { + if (!_enableRcv || !_turnOff_ || _enableTx || _transmitterRunning || _localLoop || _loopBack) + { + // + // Receiver is off, we're currently transmitting, or we're in loopback mode, we do nothing. + // + } + else + { + // + // See if there's a packet to pick up. + // + MemoryStream packetStream = null; + + _readerLock.EnterWriteLock(); + if (!_receiverRunning && _pendingPackets.Count > 0) + { + // We have a packet, dequeue it and dump it into the receiver input queue. + packetStream = _pendingPackets.Dequeue(); + } + _readerLock.ExitWriteLock(); + + if (packetStream != null) + { + // + // Read the stream into the receiver input queue. + // + packetStream.Seek(0, SeekOrigin.Begin); + + while (packetStream.Position < packetStream.Length) + { + _inputPacket.Enqueue((ushort)((packetStream.ReadByte() << 8) | packetStream.ReadByte())); + } + + // + // Skip the preamble state (only used in loopback) + // + _receiverState = ReceiverState.Data; + + // + // Alert the microcode to the presence of input data and start processing. + // + RunReceiver(); + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Receive: Incoming packet queued into input buffer."); + } + } + + // + // Schedule the next poll callback. + // + _system.Scheduler.Schedule(_receiverPollInterval, ReceiverPollCallback); + } + + private void ReceiveCallback(ulong skewNsec, object context) + { + // + // Pull the next word from the input packet and run the state machine. + // + if (_inputPacket.Count > 0) + { + ushort nextWord = _inputPacket.Dequeue(); + + switch (_receiverState) + { + case ReceiverState.Preamble: + if (nextWord == 0x55d5) // end of preamble + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Receive: end of preamble, switching to Data state."); + _receiverState = ReceiverState.Data; + } + break; + + case ReceiverState.Data: + // + // Stuff into FIFO. + // + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Receive: Enqueuing Data word 0x{0:x4} onto FIFO, {1} words left.", nextWord, _inputPacket.Count); + _fifo.Enqueue(nextWord); + _crc32.AddToChecksum(nextWord); + UpdateWakeup(); + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Packet CRC is now 0x{0:x8}", _crc32.Checksum); + break; + } + } + + if (_inputPacket.Count > 0) + { + // + // Post next event if there are still words left. + // + _receiveEvent = _system.Scheduler.Schedule(_transmitInterval, ReceiveCallback); + } + else + { + // + // End of packet. + // + _receiverRunning = false; + + // + // Let microcode know the packet is done. + // + _inAttn = true; + + // + // Update CRC and other flags. + // + _rxMode_ = false; + + _rxGoodCRC = _loopBack || _localLoop ? _crc32.Checksum == _goodCRC : true; + + UpdateWakeup(); + + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Final Packet CRC is 0x{0:x8}", _crc32.Checksum); + } + } + + private void UpdateWakeup() + { + // + // See schematic, pg 2; ethernet requests (wakeups) generated by: // TxMode & BufIR & Defer' & LastWord' (i.e.transmit on, fifo buffer not full, not deferring, not the last word) // OR // Defer & TickElapsed (microcode asked for the transmission to be deferred, and that deferral time has elapsed) // OR // RcvMode & BufOR & Purge' (i.e. rcv on, fifo data ready, not purging fifo) // OR // Attn (i.e.hardware has a a status to report) + // + bool txWakeup = _enableTx && _fifo.Count < 16 && !_defer && !_lastWord; + bool deferWakeup = _defer & _tickElapsed; + bool rxWakeup = !_rxMode_ && _fifo.Count > 2 && !_purge; + + if (txWakeup || deferWakeup || rxWakeup || _outAttn || _inAttn) + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Waking Ethernet task (tx {0} defer {1} rx {2} outAttn {3} inAttn {4}", txWakeup, deferWakeup, rxWakeup, _outAttn, _inAttn); + _system.CP.WakeTask(TaskType.Ethernet); + } + else + { + if (Log.Enabled) Log.Write(LogComponent.EthernetControl, "Sleeping Ethernet task."); + _system.CP.SleepTask(TaskType.Ethernet); + } + } + + private DSystem _system; + + // Output data + private bool _outputDataLatched; + private ushort _outputData; + private Queue _fifo; + + // Input data + private Queue _inputPacket; + private Queue _pendingPackets; + + // Defer timings + private bool _tickElapsed; + + // Attention flags + private bool _outAttn; + private bool _inAttn; + + private bool _purge; + + // Status Bit(in Xerox order) + // -------------------------------- + // TurnOff' : 15 + // R.EvenLen : 14 + // R.GoodCRC : 13 + // R.Overrun' : 12 + // R.GoodAlign : 11 + // T.Underrun' : 10 + // T.Collision' : 9 + // RcvMode' : 8 + // EnableTrn : 7 + // LastWord : 6 + // EnableRcv : 5 + // LocalLoop : 4 + // Loopback : 3 + + // DiagVideoData : 2 + // VideoClock : 1 // Used by LSEP + // DiagLineSync : 0 + private bool _turnOff_; private bool _rxEvenLen; private bool _rxGoodCRC; private bool _rxOverrun_; private bool _rxGoodAlign; private bool _txUnderrun; private bool _txCollision_; private bool _rxMode_; private bool _enableTx; private bool _lastWord; private bool _enableRcv; private bool _localLoop; private bool _loopBack; + + // EOCtl: Bit(etc) + // ---------------------------- + // EnableTrn 15 + // LastWord 14 + // Defer 13 + private bool _defer; + + + // EICtl: Bit(xerox order) // ------------------------------------ // EnableRcv 15 // TurnOff' 14 // LocalLoop 13 // LoopBack 12 + // (See above) + + // + // Defer event & timing -- 51.2uS + // + private Event _deferEvent; + private readonly ulong _deferDelay = (ulong)(51.2 * Conversion.UsecToNsec); + + // + // Transmit event and timing -- 1600nS + // + private readonly ulong _transmitInterval = 1200; + private readonly ulong _ipgInterval = (ulong)(9.6 * Conversion.UsecToNsec); // Inter-packet gap + private bool _transmitterRunning; + private Event _transmitEvent; + + // + // Receive event, timing, and thread safety + // + private readonly ulong _receiveInterval = 1200; + private readonly ulong _receiveIntervalLoopback = 25600; + private bool _receiverRunning; + private ReceiverState _receiverState; + private Event _receiveEvent; + + private readonly ulong _receiverPollInterval = (ulong)(51.2 * Conversion.UsecToNsec); + + private ReaderWriterLockSlim _readerLock; + + private enum ReceiverState + { + Off, + Preamble, + Data + } + + // + // CRC32 generator + // + private CRC32 _crc32; + // private const uint _goodCRC = 0xc704dd7b; + private const uint _goodCRC = 0x2144df1c; // This is not the correct residual for Ethernet but it's what I'm getting right now... + + // + // Host ethernet + // + private IPacketInterface _hostInterface; + private Queue _outputPacket; + + } +} diff --git a/D/Ethernet/HostEthernet.cs b/D/Ethernet/HostEthernet.cs new file mode 100644 index 0000000..2d247a5 --- /dev/null +++ b/D/Ethernet/HostEthernet.cs @@ -0,0 +1,271 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using SharpPcap; +using SharpPcap.WinPcap; +using SharpPcap.LibPcap; +using SharpPcap.AirPcap; +using PacketDotNet; + +using System; +using System.Net.NetworkInformation; + + +using D.Logging; +using PacketDotNet.Utils; +using System.Text; + +namespace D.Ethernet +{ + /// + /// Represents a host ethernet interface. + /// + public struct EthernetInterface + { + public EthernetInterface(string name, string description) + { + Name = name; + Description = description; + } + + public override string ToString() + { + return String.Format("{0} ({1})", Name, Description); + } + + public string Name; + public string Description; + } + + /// + /// Implements the logic for sending and receiving emulated 10mbit ethernet packets over an actual + /// ethernet interface controlled by the host operating system. + /// + /// This uses SharpPcap to do the dirty work. + /// + public class HostEthernetEncapsulation : IPacketInterface + { + public HostEthernetEncapsulation(string name) + { + // Find the specified device by name + foreach (ICaptureDevice device in CaptureDeviceList.Instance) + { + if (device is WinPcapDevice) + { + // + // We use the friendly name to make it easier to specify in config files. + // + if (((WinPcapDevice)device).Interface.FriendlyName.ToLowerInvariant() == name.ToLowerInvariant()) + { + AttachInterface(device); + break; + } + } + else + { + if (device.Name.ToLowerInvariant() == name.ToLowerInvariant()) + { + AttachInterface(device); + break; + } + } + } + + if (_interface == null) + { + Log.Write(LogComponent.HostEthernet, "Specified ethernet interface does not exist or is not compatible with Darkstar."); + throw new InvalidOperationException("Specified ethernet interface does not exist or is not compatible with Darkstar."); + } + + UpdateSourceAddress(); + } + + public void RegisterReceiveCallback(ReceivePacketDelegate callback) + { + _callback = callback; + + // Now that we have a callback we can start receiving stuff. + Open(true /* promiscuous */, 0); + BeginReceive(); + } + + public void Shutdown() + { + if (_interface != null) + { + try + { + if (_interface.Started) + { + _interface.StopCapture(); + } + } + catch + { + // Eat exceptions. The Pcap libs seem to throw on StopCapture on + // Unix platforms, we don't really care about them (since we're shutting down anyway) + // but this prevents debug spew from appearing on the console. + } + finally + { + _interface.Close(); + } + } + } + + /// + /// Sends an array of words over the ethernet. + /// + /// + /// + public void Send(ushort[] packet) + { + byte[] packetBytes = new byte[packet.Length * 2]; + + // StringBuilder sb = new StringBuilder(); + // + // Do this annoying dance to stuff the ushorts into bytes because this is C#. + // + for (int i = 0; i < packet.Length; i++) + { + packetBytes[i * 2] = (byte)(packet[i] >> 8); + packetBytes[i * 2 + 1] = (byte)(packet[i]); + + // sb.AppendFormat("{0:x2} {1:x2} ", packetBytes[i * 2], packetBytes[i * 2 + 1]); + } + + ByteArraySegment seg = new ByteArraySegment(packetBytes); + + EthernetPacket p = new EthernetPacket(seg); + + // Send it over the 'net! + _interface.SendPacket(p); + + Log.Write(LogComponent.HostEthernet, "Ethernet packet (length {0}) sent.", packetBytes.Length); + // Log.Write(LogComponent.HostEthernet, "Contents: {0}", sb.ToString()); + } + + private void ReceiveCallback(object sender, CaptureEventArgs e) + { + // + // Filter out packets intended for the emulator, forward them on, drop everything else. + // + if (e.Packet.LinkLayerType == LinkLayers.Ethernet) + { + // + // We wrap this in a try/catch; on occasion Packet.ParsePacket fails due to a bug + // in the PacketDotNet library. + // + EthernetPacket packet = null; + try + { + packet = (EthernetPacket)Packet.ParsePacket(LinkLayers.Ethernet, e.Packet.Data); + } + catch (Exception ex) + { + // Just eat this, log a message. + Log.Write(LogType.Error, LogComponent.HostEthernet, "Failed to parse incoming packet. Exception {0}", ex.Message); + packet = null; + } + + if (packet != null) + { + if (!packet.SourceHwAddress.Equals(_10mbitSourceAddress) && + packet.PayloadData != null) // Don't recieve packets sent by this emulator. + { + Log.Write(LogComponent.HostEthernet, "Received 10mbit packet."); + _callback(new System.IO.MemoryStream(packet.PayloadData)); + } + else + { + // Not for us, discard the packet. + } + } + } + } + + private void UpdateSourceAddress() + { + byte[] macBytes = new byte[6]; + + for (int i = 0; i < 6; i++) + { + macBytes[i] = (byte)((Configuration.HostID >> (i * 8))); + } + + _10mbitSourceAddress = new PhysicalAddress(macBytes); + + } + + private void AttachInterface(ICaptureDevice iface) + { + _interface = iface; + + if (_interface == null) + { + throw new InvalidOperationException("Requested interface not found."); + } + + Log.Write(LogComponent.HostEthernet, "Attached to host interface {0}", iface.Name); + } + + private void Open(bool promiscuous, int timeout) + { + if (_interface is WinPcapDevice) + { + ((WinPcapDevice)_interface).Open(promiscuous ? OpenFlags.MaxResponsiveness | OpenFlags.Promiscuous : OpenFlags.MaxResponsiveness, timeout); + } + else if (_interface is LibPcapLiveDevice) + { + ((LibPcapLiveDevice)_interface).Open(promiscuous ? DeviceMode.Promiscuous : DeviceMode.Normal, timeout); + } + else if (_interface is AirPcapDevice) + { + ((AirPcapDevice)_interface).Open(promiscuous ? OpenFlags.MaxResponsiveness | OpenFlags.Promiscuous : OpenFlags.MaxResponsiveness, timeout); + } + + Log.Write(LogComponent.HostEthernet, "Host interface opened and receiving packets."); + } + + /// + /// Begin receiving packets, forever. + /// + private void BeginReceive() + { + // Kick off receiver. + _interface.OnPacketArrival += ReceiveCallback; + _interface.StartCapture(); + } + + private ICaptureDevice _interface; + private ReceivePacketDelegate _callback; + + private PhysicalAddress _10mbitSourceAddress; + } +} diff --git a/D/Ethernet/IPacketInterface.cs b/D/Ethernet/IPacketInterface.cs new file mode 100644 index 0000000..ca8e6aa --- /dev/null +++ b/D/Ethernet/IPacketInterface.cs @@ -0,0 +1,60 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System.IO; + +namespace D.Ethernet +{ + public delegate void ReceivePacketDelegate(MemoryStream data); + + /// + /// Provides a generic interface for host network devices that can encapsulate + /// Ethernet packets. + /// + public interface IPacketInterface + { + /// + /// Registers a callback delegate to handle packets that are received. + /// + /// + void RegisterReceiveCallback(ReceivePacketDelegate callback); + + /// + /// Sends the specified word array over the device. + /// + /// + /// + void Send(ushort[] packet); + + /// + /// Shuts down the encapsulation provider. + /// + void Shutdown(); + } +} diff --git a/D/HighResTimer.cs b/D/HighResTimer.cs new file mode 100644 index 0000000..c5848aa --- /dev/null +++ b/D/HighResTimer.cs @@ -0,0 +1,230 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; + +namespace D +{ + /// + /// HighResTimer gives us access to NT's very-high-resolution PerformanceCounters. + /// This gives us the precision we need to sync emulation to any speed we desire. + /// + public sealed class HighResTimer + { + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceCounter( + out long lpPerformanceCount); + + [DllImport("Kernel32.dll")] + private static extern bool QueryPerformanceFrequency( + out long lpFrequency); + + public HighResTimer() + { + // What's the frequency, Kenneth? + if (QueryPerformanceFrequency(out _frequency) == false) + { + // high-performance counter not supported + throw new Win32Exception(); + } + } + + /// + /// Returns the current time in seconds. + /// + /// + public double GetCurrentTime() + { + long currentTime; + QueryPerformanceCounter(out currentTime); + + return (double)(currentTime) / (double)_frequency; + } + + private long _frequency; + } + + public sealed class FrameTimer + { + [DllImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] + static extern UInt32 TimeGetDevCaps(ref TimeCaps timeCaps, UInt32 sizeTimeCaps); + + [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + static extern UInt32 TimeBeginPeriod(UInt32 uPeriod); + + [DllImport("winmm.dll", EntryPoint = "timeEndPeriod")] + public static extern uint TimeEndPeriod(uint uMilliseconds); + + [DllImport("kernel32.dll", EntryPoint = "CreateTimerQueue")] + public static extern IntPtr CreateTimerQueue(); + + [DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueEx")] + public static extern bool DeleteTimerQueue(IntPtr hTimerQueue, IntPtr hCompletionEvent); + + [DllImport("kernel32.dll", EntryPoint = "CreateTimerQueueTimer")] + public static extern bool CreateTimerQueueTimer(out IntPtr phNewTimer, IntPtr hTimerQueue, IntPtr Callback, IntPtr Parameter, UInt32 DueTime, UInt32 Period, uint Flags); + + [DllImport("kernel32.dll", EntryPoint = "ChangeTimerQueueTimer")] + public static extern bool ChangeTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, UInt32 DueTime, UInt32 Period); + + + [DllImport("kernel32.dll", EntryPoint = "DeleteTimerQueueTimer")] + public static extern bool DeleteTimerQueueTimer(IntPtr hTimerQueue, IntPtr hTimer, IntPtr hCompletionEvent); + + [StructLayout(LayoutKind.Sequential)] + public struct TimeCaps + { + public UInt32 wPeriodMin; + public UInt32 wPeriodMax; + }; + + [UnmanagedFunctionPointer(CallingConvention.Winapi)] + public delegate void UnmanagedTimerCallback(IntPtr param, bool timerOrWait); + + /// + /// FrameTimer provides a simple method to synchronize execution to a given framerate. + /// Calling WaitForFrame() blocks until the start of the next frame. + /// + /// NOTE: This code uses the Win32 TimerQueue APIs instead of the System.Threading.Timer + /// APIs because the .NET APIs do not allow execution of the callback on the timer's thread -- + /// it queues up a new worker thread. This lowers the accuracy of the timer, and since we + /// need all the precision we can get they're not suitable here. + /// + /// The frame rate to sync to. + public FrameTimer(double framesPerSecond) + { + // + // Set the timer to the minimum value (1ms). This should be supported on any modern x86 system. + // If not, too bad... + // + UInt32 res = TimeBeginPeriod(1); + + if (res != 0) + { + throw new InvalidOperationException("Unable to set timer period."); + } + + // + // Create a new timer queue + // + _hTimerQueue = CreateTimerQueue(); + + if (_hTimerQueue == IntPtr.Zero) + { + throw new InvalidOperationException("Unable to create timer queue."); + } + + // + // Since we only have a resolution of 1ms, we have to do some hackery to get a slightly more accurate framerate. + // (60 fields/sec requires 16 2/3s ms frame delay.) + // We alternate between two rates at varying intervals and this gets us fairly close to the desired frame rate. + // + _callback = new UnmanagedTimerCallback(TimerCallbackFn); + + _highPeriod = (uint)Math.Ceiling(1000.0 * (1.0 / framesPerSecond)); + _lowPeriod = (uint)Math.Floor(1000.0 * (1.0 / framesPerSecond)); + _periodTenths = _periodSwitch = (uint)((1000.0 * (1.0 / framesPerSecond) - Math.Floor(1000.0 * (1.0 / framesPerSecond))) * 10.0); + + if (!CreateTimerQueueTimer(out _hTimer, _hTimerQueue, Marshal.GetFunctionPointerForDelegate(_callback), IntPtr.Zero, _lowPeriod, _lowPeriod, 0x00000020 /* execute in timer thread */)) + { + throw new InvalidOperationException("Unable to create timer queue timer."); + } + + _event = new AutoResetEvent(false); + + _lowTimer = 0; + } + + ~FrameTimer() + { + // + // Clean stuff up + // + try + { + DeleteTimerQueueTimer(_hTimerQueue, _hTimer, IntPtr.Zero); + DeleteTimerQueue(_hTimerQueue, IntPtr.Zero); + } + catch + { + // Eat exceptions (for Mono) + } + + // + // Fire off a final event to release any call that's waiting... + // + if (_event != null) + { + _event.Set(); + } + } + + /// + /// Waits for the timer to fire. + /// + public void WaitForFrame() + { + _event.WaitOne(); + } + + /// + /// Callback from timer queue. Work done here is executed on the timer's thread, so must be quick. + /// + /// + /// + private void TimerCallbackFn(IntPtr lpParameter, bool TimerOrWaitFired) + { + _event.Set(); + _lowTimer++; + + if (_lowTimer >= _periodSwitch) + { + _lowTimer = 0; + _period = !_period; + ChangeTimerQueueTimer(_hTimerQueue, _hTimer, _period ? _lowPeriod : _highPeriod, _period ? _lowPeriod : _highPeriod); + + _periodSwitch = !_period ? _periodTenths : 10 - _periodTenths; + } + } + + private IntPtr _hTimerQueue; + private IntPtr _hTimer; + private AutoResetEvent _event; + private UnmanagedTimerCallback _callback; + private uint _lowPeriod; + private uint _highPeriod; + private uint _periodSwitch; + private uint _periodTenths; + private int _lowTimer; + private bool _period; + } +} \ No newline at end of file diff --git a/D/IO/FloppyDisk.cs b/D/IO/FloppyDisk.cs new file mode 100644 index 0000000..31360eb --- /dev/null +++ b/D/IO/FloppyDisk.cs @@ -0,0 +1,402 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace D.IO +{ + /// + /// Presents data for a floppy disk, organized by cylinder, head, and sector, + /// and provides constructors for loading from IMD file. + /// + public class FloppyDisk + { + public FloppyDisk(string imagePath) + { + _imagePath = imagePath; + _tracks = new Track[2, 77]; + _isSingleSided = true; + + using (FileStream fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read)) + { + LoadIMD(fs); + } + } + + public string Description + { + get { return _imdHeader; } + } + + public bool IsSingleSided + { + get { return _isSingleSided; } + } + + public string ImagePath + { + get { return _imagePath; } + } + + /// + /// Returns sector data for the given address. + /// + /// + /// + /// + /// + public Sector GetSector(int cylinder, int head, int sector) + { + return _tracks[head, cylinder].ReadSector(sector); + } + + public Track GetTrack(int cylinder, int head) + { + return _tracks[head, cylinder]; + } + + private void LoadIMD(Stream s) + { + _imdHeader = ReadIMDHeader(s); + + // + // Read each track in and place it in memory. + // We assume that there will be no more than 77 cylinders + // and no more than 2 tracks. We also do a basic sanity + // check that no track appears more than once. + // + while (true) + { + Track t = new Track(s); + + if (t.Cylinder < 0 || t.Cylinder > 76) + { + throw new InvalidOperationException(String.Format("Invalid cylinder value {0}", t.Cylinder)); + } + + if (t.Head < 0 || t.Head > 1) + { + throw new InvalidOperationException(String.Format("Invalid head value {0}", t.Head)); + } + + if (_tracks[t.Head, t.Cylinder] != null) + { + throw new InvalidOperationException(String.Format("Duplicate head/track", t.Head, t.Cylinder)); + } + + if (t.Head != 0) + { + // Got a track on side 1, this must be a double-sided disk. + _isSingleSided = false; + } + + _tracks[t.Head, t.Cylinder] = t; + + if (s.Position == s.Length) + { + // End of file. + break; + } + } + } + + private string ReadIMDHeader(Stream s) + { + StringBuilder sb = new StringBuilder(); + while (true) + { + byte b = (byte)s.ReadByte(); + + if (b == 0x1a) + { + break; + } + else + { + sb.Append((char)b); + } + } + + return sb.ToString(); + } + + private string _imdHeader; + private bool _isSingleSided; + private string _imagePath; + + private Track[,] _tracks; + } + + /// + /// Represents a single track's worth of sectors + /// + public class Track + { + /// + /// Create a new, empty track with the specified format, sector size and sector count. + /// + /// + /// + /// + /// + /// + public Track(Format format, int cylinder, int head, int sectorCount, int sectorSize) + { + _format = format; + _cylinder = cylinder; + _head = head; + _sectorCount = sectorCount; + _sectorSize = sectorSize; + _sectors = new Sector[_sectorCount]; + + for (int i = 0; i < _sectorCount; i++) + { + _sectors[i] = new Sector(_sectorSize, _format); + } + } + + /// + /// Create a new track loaded from the given stream. The stream is expected to be positioned + /// at the beginning of an IMD sector definition. + /// + /// + public Track(Stream s) + { + bool bCylMap = false; + bool bHeadMap = false; + + _format = (Format)s.ReadByte(); + _cylinder = s.ReadByte(); + _head = s.ReadByte(); + _sectorCount = s.ReadByte(); + int sectorSizeIndex = s.ReadByte(); + + // Basic sanity check of values + if (_format > Format.MFM250 || + _cylinder > 77 || + (_head & 0x3f) > 1 || + sectorSizeIndex > _sectorSizes.Length - 1) + { + throw new InvalidOperationException("Invalid header data for track."); + } + + _sectorSize = _sectorSizes[sectorSizeIndex]; + + bCylMap = (_head & 0x80) != 0; + bHeadMap = (_head & 0x40) != 0; + + // Head is just the first bit. + _head = (byte)(_head & 0x1); + + // + // Read sector numbering + // + _sectorOrdering = new List(_sectorCount); + + for (int i = 0; i < _sectorCount; i++) + { + _sectorOrdering.Add(s.ReadByte()); + } + + // + // At this time, cyl and head maps are not supported. + // It's not expected any Star disk would use such a format. + // + if (bCylMap | bHeadMap) + { + throw new NotImplementedException("IMD Cylinder and Head maps not supported."); + } + + // + // Read the sector data in. + // + _sectors = new Sector[_sectorCount]; + for (int i = 0; i < _sectorCount; i++) + { + SectorRecordType type = (SectorRecordType)s.ReadByte(); + byte compressedData; + + switch (type) + { + case SectorRecordType.Unavailable: + // Nothing, sectors left null. + break; + + case SectorRecordType.Normal: + case SectorRecordType.NormalDeleted: + case SectorRecordType.NormalError: + case SectorRecordType.DeletedError: + _sectors[_sectorOrdering[i] - 1] = new Sector(_sectorSize, _format, s); + break; + + case SectorRecordType.Compressed: + case SectorRecordType.CompressedDeleted: + case SectorRecordType.CompressedError: + case SectorRecordType.CompressedDeletedError: + compressedData = (byte)s.ReadByte(); + + // Fill sector with compressed data + _sectors[_sectorOrdering[i] - 1] = new Sector(_sectorSize, _format, compressedData); + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected IMD sector data type {0}", type)); + } + } + } + + public int Cylinder + { + get { return _cylinder; } + } + + public int Head + { + get { return _head; } + } + + public int SectorCount + { + get { return _sectorCount; } + } + + public int SectorSize + { + get { return _sectorSize; } + } + + public Format Format + { + get { return _format; } + } + + public Sector ReadSector(int sector) + { + return _sectors[sector]; + } + + // + // 00 Sector data unavailable - could not be read + // 01 .... Normal data: (Sector Size) bytes follow + // 02 xx Compressed: All bytes in sector have same value(xx) + // 03 .... Normal data with "Deleted-Data address mark" + // 04 xx Compressed with "Deleted-Data address mark" + // 05 .... Normal data read with data error + // 06 xx Compressed read with data error + // 07 .... Deleted data read with data error + // 08 xx Compressed, Deleted read with data error + // + private enum SectorRecordType + { + Unavailable = 0, + Normal = 1, + Compressed = 2, + NormalDeleted = 3, + CompressedDeleted = 4, + NormalError = 5, + CompressedError = 6, + DeletedError = 7, + CompressedDeletedError = 8, + } + + private Format _format; + private int _cylinder; + private int _head; + private int _sectorCount; + private int _sectorSize; + + private List _sectorOrdering; + + private Sector[] _sectors; + + private static int[] _sectorSizes = { 128, 256, 512, 1024, 2048, 4096, 8192 }; + } + + public class Sector + { + public Sector(int sectorSize, Format format) + { + _data = new byte[sectorSize]; + _format = format; + } + + public Sector(int sectorSize, Format format, byte compressedValue) + : this(sectorSize, format) + { + for (int i = 0; i < _data.Length; i++) + { + _data[i] = compressedValue; + } + } + + public Sector(int sectorSize, Format format, Stream s) + : this(sectorSize, format) + { + int read = s.Read(_data, 0, sectorSize); + + if (read != sectorSize) + { + throw new InvalidOperationException("Short read in sector data."); + } + } + + public Format Format + { + get { return _format; } + } + + public byte[] Data + { + get { return _data; } + } + + private Format _format; + + private byte[] _data; + } + + // 00 = 500 kbps FM \ Note: kbps indicates transfer rate, + // 01 = 300 kbps FM > not the data rate, which is + // 02 = 250 kbps FM / 1/2 for FM encoding. + // 03 = 500 kbps MFM + // 04 = 300 kbps MFM + // 05 = 250 kbps MFM + public enum Format + { + FM500 = 0, + FM300 = 1, + FM250 = 2, + MFM500 = 3, + MFM300 = 4, + MFM250 = 5, + } +} diff --git a/D/IO/FloppyDrive.cs b/D/IO/FloppyDrive.cs new file mode 100644 index 0000000..ae29b1c --- /dev/null +++ b/D/IO/FloppyDrive.cs @@ -0,0 +1,183 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; + +using D.IO; +using D.Logging; + +namespace D +{ + public class FloppyDrive + { + public FloppyDrive(DSystem system) + { + _system = system; + // + // Start the Index event rolling. This will run forever. + // + _system.Scheduler.Schedule(_indexInterval, IndexCallback); + + Reset(); + } + + public FloppyDisk Disk + { + get { return _disk; } + } + + public int Track + { + get { return _track; } + } + + public bool IsLoaded + { + get { return _disk != null; } + } + + public bool IsWriteProtected + { + get { return _writeProtected; } + } + + public bool IsSingleSided + { + get { return _singleSided; } + } + + public bool Track0 + { + get { return _track == 0; } + } + + public bool Index + { + get { return _index; } + } + + public bool DiskChange + { + get { return _diskChange; } + } + + public bool DriveSelect + { + get { return _driveSelect; } + set + { + _driveSelect = value; + + // + // The Disk Change signal is reset when + // Drive Select goes low. + // + if (!_driveSelect) + { + _diskChange = false; + } + } + } + + public void Reset() + { + _track = 0; + _singleSided = false; + _writeProtected = false; + _diskChange = false; + _index = false; + _driveSelect = false; + } + + public void LoadDisk(FloppyDisk disk) + { + _disk = disk; + _singleSided = _disk.IsSingleSided; + _diskChange = true; + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Floppy disk image loaded. Description is:\n{0}", disk.Description); + + // TODO: update WP, SS bits, etc. + } + + public void UnloadDisk() + { + // TODO: Commit disk changes + _disk = null; + _diskChange = true; + } + + public void SeekTo(int track) + { + // Clip into range. + _track = Math.Max(0, track); + _track = Math.Min(76, _track); + } + + private void IndexCallback(ulong skewNsec, object context) + { + // + // This always runs even when a disk isn't loaded or spinning. + // It only actually modifies _index if a disk is loaded. + // We need to generate index pulses anyway (even if they are + // inaccurate and unused by us given that we're not emulating + // the disk at that low of a level) this does the job. + // + if (DriveSelect && IsLoaded && !_index) + { + // Raise the index signal, hold for a short period. + _index = true; + _system.Scheduler.Schedule(_indexDuration, IndexCallback); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Disk rotation complete, raising INDEX signal for 10us."); + } + else + { + // Reset the index signal, wait for a long period (for the disk to go round again). + _index = false; + _system.Scheduler.Schedule(_indexInterval, IndexCallback); + } + + } + + private DSystem _system; + + private bool _singleSided; + private bool _writeProtected; + private int _track; + private bool _diskChange; + private bool _driveSelect; + private FloppyDisk _disk; + + // Index signal and timing + private bool _index; + private ulong _indexInterval = 200 * Conversion.MsecToNsec; // 1/5 second at 300rpm + private ulong _indexDuration = 10 * Conversion.UsecToNsec; // 10uSec duration for index signal. + } +} diff --git a/D/IO/SA1000.cs b/D/IO/SA1000.cs new file mode 100644 index 0000000..9947730 --- /dev/null +++ b/D/IO/SA1000.cs @@ -0,0 +1,477 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; +using System.IO; + +namespace D.IO +{ + public enum DriveType + { + Invalid = 0, + SA1004 = 1, // 10MB + Q2040 = 2, // Quantum 40MB + Q2080 = 3, // Quantum 80MB + } + + /// + /// Geometry for an SA1000-style drive + /// + public struct Geometry + { + public Geometry(int cylinders, int heads) + { + Cylinders = cylinders; + Heads = heads; + } + + public int Cylinders; + public int Heads; + + public static Geometry SA1004 = new Geometry(256, 4); + public static Geometry Q2040 = new Geometry(512, 8); + public static Geometry Q2080 = new Geometry(1172, 7); + } + + /// + /// Encapsulates the state, disk data and low-level behavior of Shugart SA1000-style drives. + /// + public class SA1000Drive + { + public SA1000Drive(DSystem system) + { + _type = DriveType.Invalid; + NewDisk(_type, String.Empty); + + _system = system; + + // Queue up the event that rotates our virtual disk. This runs continuously. + _system.Scheduler.Schedule(_diskWordDelay, DiskWordCallback); + + Reset(); + } + + public void Reset() + { + _cylinder = 0; + _head = 0; + _wordIndex = 0; + _index = false; + _seekComplete = true; + _lastStep = false; + } + + public void Save() + { + if (!string.IsNullOrEmpty(_diskImagePath)) + { + Save(_diskImagePath); + } + } + + public void Save(string path) + { + using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write)) + { + // + // Format is: + // 1st Byte: Drive type (see DriveType enumeration) + // Bytes 2-N: Tracks of data (5325 words each) for all cylinders on the disk + // Each word in a track is encapsulated in a 24-bit integer: + // Metadata (address mark, CRC indicators) in upper 8 bits, data in low 16 bits. + // + fs.WriteByte((byte)_type); + + for (int cyl = 0; cyl < _geometry.Cylinders; cyl++) + { + for (int head = 0; head < _geometry.Heads; head++) + { + for (int word = 0; word < _wordsPerTrack; word++) + { + fs.Write(BitConverter.GetBytes(_tracks[cyl, head, word]), 0, 3); + } + } + } + + _diskImagePath = path; + } + } + + public void Load(string path) + { + try + { + using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + byte type = (byte)fs.ReadByte(); + if (type < (int)DriveType.SA1004 || type > (int)DriveType.Q2080) + { + throw new InvalidOperationException("Unsupported drive type."); + } + + _type = (DriveType)type; + + NewDisk(_type, path); + + byte[] buffer = new byte[4]; + for (int cyl = 0; cyl < _geometry.Cylinders; cyl++) + { + for (int head = 0; head < _geometry.Heads; head++) + { + for (int word = 0; word < _wordsPerTrack; word++) + { + int read = fs.Read(buffer, 0, 3); + + if (read < 3) + { + throw new InvalidOperationException("Short read on disk image load."); + } + + _tracks[cyl, head, word] = BitConverter.ToUInt32(buffer, 0); + } + } + } + } + } + catch(Exception e) + { + // + // Hit an exception while loading, ensure that we don't + // have a partial image loaded. + // + _type = DriveType.Invalid; + NewDisk(_type, String.Empty); + + throw e; + } + } + + public void NewDisk(DriveType type, string path) + { + switch (type) + { + case DriveType.Invalid: + // + // For Invalid (i.e. unloaded or unspecified disks) + // we assume an SA1004 geometry, but will always + // return Not Ready. + // + case DriveType.SA1004: + _geometry = Geometry.SA1004; + break; + + case DriveType.Q2040: + _geometry = Geometry.Q2040; + break; + + case DriveType.Q2080: + _geometry = Geometry.Q2080; + break; + } + + _tracks = new uint[_geometry.Cylinders, _geometry.Heads, _wordsPerTrack]; + _type = type; + _diskImagePath = path; + } + + public string ImagePath + { + get { return _diskImagePath; } + } + + public DriveType Type + { + get { return _type; } + } + + public Geometry Geometry + { + get { return _geometry; } + } + + public int WordsPerTrack + { + get { return _wordsPerTrack; } + } + + public int Cylinder + { + get { return _cylinder; } + } + + public int Head + { + get { return _head; } + } + + public bool Track0 + { + get { return _cylinder == 0; } + } + + public bool Index + { + get { return _index; } + } + + public bool IsReady + { + // + // We're always spun up and ready as + // long as a valid disk is loaded. + // + get { return _type != DriveType.Invalid; } + } + + public bool SeekComplete + { + get { return _seekComplete; } + } + + public int WordIndex + { + get { return _wordIndex; } + } + + public void SetHead(int head) + { + _head = head % _geometry.Heads; + } + + public void Step(bool directionIn, bool stepSignal) + { + if (stepSignal && !_lastStep) + { + if (_stepCount == 0) + { + _directionIn = directionIn; + } + + // We queue up the step operation + _timeSinceLastStep = 0; + _stepCount++; + + _seekComplete = false; + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Buffering step."); + } + + _lastStep = stepSignal; + } + + public uint ReadData() + { + return _currentWord; + } + + public void WriteData(ushort data) + { + _tracks[_cylinder, _head, _wordIndex] = data; + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Wrote 0x{0:x4} to c/h/w {1}/{2}/{3}", data, _cylinder, _head, _wordIndex); + } + + public void WriteAddressMark(ushort data) + { + _tracks[_cylinder, _head, _wordIndex] = (uint)(data | 0x10000); // Set AM bit + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Wrote Address Mark 0x{0:x4} to c/h/w {1}/{2}/{3}", data, _cylinder, _head, _wordIndex); + } + + public void WriteCRC(ushort data) + { + _tracks[_cylinder, _head, _wordIndex] = (uint)(data | 0x20000); // Set CRC bit + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Wrote CRC 0x{0:x4} to c/h/w {1}/{2}/{3}", data, _cylinder, _head, _wordIndex); + } + + public uint DebugRead(int cylinder, int head, int word) + { + return _tracks[cylinder, head, word]; + } + + /// + /// This gets called back every 4.32uS at which point we move a new word under the head of the disk, + /// wake up the disk task as appropriate, and deal with buffered seeks if any are in progress. + /// + /// + /// + private void DiskWordCallback(ulong skewNsec, object context) + { + // + // Rotate the disk one word. If a wakeup is requested then take care of that. + // + SpinDisk(); + + // + // Let the controller know a new word is ready. + // + _system.ShugartController.SignalDiskWordReady(); + + // + // Deal with buffered seeks: if more than 350uS has elapsed since the last step + // pulse from the microcode, we will do the seek now. + // Technically this logic belongs in the drive itself but since we already have this + // convenient event running here we do it now. + // + _timeSinceLastStep += 370; + + if (_timeSinceLastStep > 35000 && _stepCount > 0) + { + Seek(_stepCount, _directionIn); + _stepCount = 0; + } + + // Queue this event up again. + _system.Scheduler.Schedule(_diskWordDelay - skewNsec, DiskWordCallback); + } + + /// + /// Performs a "buffered seek" by the requested amount + /// in the specified direction. + /// + /// + /// + private void Seek(int count, bool directionIn) + { + _destinationCylinder = _cylinder + count * (directionIn ? 1 : -1); + + // Clip into range + _destinationCylinder = Math.Max(0, _destinationCylinder); + _destinationCylinder = Math.Min(_geometry.Cylinders - 1, _destinationCylinder); + + // + // Schedule a seek for about 50ms in the future. + // This is approximate, but we don't need exact timing here. + // + _system.Scheduler.Schedule(_seekDuration, SeekCompleteCallback); + } + + private void SeekCompleteCallback(ulong skewNsec, object context) + { + // + // Move to the specified destination. + // + _cylinder = _destinationCylinder; + _seekComplete = true; + + _system.ShugartController.SignalSeekComplete(); + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Seek to {0} complete.", _cylinder); + } + + /// + /// Simulates the rotation of the disc under the drive's heads; each call + /// puts a new word of data under the "head" (which is returned by ReadData). At the end + /// of the track the Index signal is raised. + /// + /// + /// + private void SpinDisk() + { + _currentWord = _tracks[_cylinder, _head, _wordIndex]; + + _wordIndex++; + + if (_wordIndex == _wordsPerTrack) + { + _wordIndex = 0; + _index = true; + } + else + { + _index = false; + } + } + + + // + // Per the SA1000 spec, each track has a capacity of 10.4kbytes -- roughly 5325 words. + // + // We store disk data words in 32-bit words; the low 16 bits are the data, the upper 16 bits being non-zero + // indicates that the word is an Address Mark or a CRC word. + // + // The 10mb SA1004 has 256 tracks and 4 heads. + private Geometry _geometry; + private DriveType _type; + private const int _wordsPerTrack = 5325; + private uint[,,] _tracks; + + private int _wordIndex; + private uint _currentWord; + + // + // Disk addressing + // + private int _cylinder; + private int _head; + + // + // Status bits + private bool _index; + + // + // Seek timing and data + // + private ulong _seekDuration = (ulong)(25.0 * Conversion.MsecToNsec); + private int _destinationCylinder; + private bool _seekComplete; + + // + // Seek status + // + private bool _lastStep; + private int _stepCount; + private int _timeSinceLastStep; + private bool _directionIn; + + // + // Disk word timing. + // + // Time for a single word to move under the heads: ~3.6uS. + // (Disk spins at 3125rpm, meaning 0.0192 seconds/revolution. There are + // 5325 words per track -- 0.0192 / 5325 = 3.60e-6 seconds, or 3.6uS. + // To make this line up nicely with the Star microcode clock (just to make things + // more deterministic) we make the delay 27 microinstruction cycles long -- + // 3699nS. + private readonly ulong _diskWordDelay = 3699; + + // + // Image file data + // + private string _diskImagePath; + + // + // System + // + private DSystem _system; + } + +} diff --git a/D/IO/ShugartController.cs b/D/IO/ShugartController.cs new file mode 100644 index 0000000..fe49ae7 --- /dev/null +++ b/D/IO/ShugartController.cs @@ -0,0 +1,848 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using D.Logging; +using System; +using System.Collections.Generic; + +namespace D.IO +{ + /// + /// Implements the hardware end of the Shugart disk controller, currently only supplying the logic + /// for an SA1000-style drive. + /// + public class ShugartController + { + public ShugartController(DSystem system, SA1000Drive drive) + { + _system = system; + _drive = drive; + + _writePipeline = new Queue(); + } + + public void Reset() + { + _writeEnable = false; + _wakeupControl = 0; + _writeCRC = false; + _transferEnable = false; + _firmwareEnable = false; + _directionIn = false; + _step = false; + _reduceIW = false; + _faultClear = false; + _driveSelect = false; + _headSelect = 0; + _wakeupRequest = ServiceRequest.NoWakeup0; + + _verifyError = false; + _crcError = false; + _overrun = false; + _writeFault = false; + _sa1000 = true; + _sectorFound = false; + _indexFound = false; + _readWordReady = false; + + _writePipeline.Clear(); + + ResetTransfer(); + } + + public void SetKCtl(ushort value) + { + _writeEnable = (value & 0x0001) != 0; + _wakeupControl = (value & 0x0006) >> 1; + _writeCRC = (value & 0x0008) != 0; + _transferEnable = (value & 0x0010) != 0; + _firmwareEnable = (value & 0x0020) != 0; + _directionIn = (value & 0x0040) != 0; + _step = (value & 0x0080) != 0; + _reduceIW = (value & 0x0100) != 0; + _faultClear = (value & 0x0200) != 0; + _driveSelect = (value & 0x0400) != 0; + _headSelect = (value & 0xf800) >> 11; + + _wakeupRequest = (ServiceRequest)(_wakeupControl | (_transferEnable ? 0x4 : 0x0)); + + // + // "The SA1000's WriteFault is cleared by de-selecting the drive for at least 500ns." + // We're not that picky. + // + if (!_driveSelect) + { + _writeFault = false; + } + + // + // Step the drive + // + _drive.Step(_directionIn, _step); + + // Select the head + // + _drive.SetHead(_headSelect); + + // + // Handle wakeup requests that might be pending based on the control value. + // + bool wake = false; + switch (_wakeupRequest) + { + case ServiceRequest.FirmwareEnable: + wake = _firmwareEnable; + break; + + case ServiceRequest.SeekComplete: + wake = SeekComplete(); + break; + + case ServiceRequest.IndexFound: + wake = _indexFound; + break; + + case ServiceRequest.SectorFound: + wake = _sectorFound; + break; + + default: + wake = false; + break; + } + + if (_transferEnable) + { + if (_writeEnable && _wakeupRequest == ServiceRequest.WriteWordNeeded) + { + _transfer = TransferType.Write; + } + else if (!_writeEnable && _wakeupRequest == ServiceRequest.WriteWordNeeded) + { + _transfer = TransferType.Verify; + } + else if (!_writeEnable && _transferEnable && _wakeupRequest == ServiceRequest.ReadWordReady) + { + _transfer = TransferType.Read; + } + else + { + throw new InvalidOperationException("Unexpected combination of service request and write enable flags"); + } + } + else + { + // + // Reset transfer state machine if _transferEnable is low. + // + ResetTransfer(); + } + + if (wake) + { + _system.CP.WakeTask(TaskType.Disk); + } + else + { + _system.CP.SleepTask(TaskType.Disk); + } + + /* + if (Log.Enabled) Log.Write( + LogType.Verbose, + LogComponent.ShugartControl, + "KCtl<-0x{0:x4} : we {1} wc {2} wcrc {3} te {4} fe {5} di {6} st {7} ri {8} fc {9} ds {10} hs {11} wq {12}", + value, + _writeEnable, + _wakeupControl, + _writeCRC, + _transferEnable, + _firmwareEnable, + _directionIn, + _step, + _reduceIW, + _faultClear, + _driveSelect, + _headSelect, + _wakeupRequest); */ + } + + public void SetKCmd(ushort value) + { + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.ShugartControl, "KCmd<-0x{0:x4} unimplemented.", value); + } + + public void ClrKFlags() + { + // This depends on the kind of service request currently set. + // TODO: figure out exactly what this works out to... + if (_wakeupRequest != ServiceRequest.FirmwareEnable && _wakeupRequest != ServiceRequest.SeekComplete) + { + _system.CP.SleepTask(TaskType.Disk); + } + + _verifyError = false; + _crcError = false; + _overrun = false; + _indexFound = false; + _sectorFound = false; + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.ShugartControl, "ClrKFlags"); + } + + public void KStrobe() + { + if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.ShugartControl, "KStrobe unimplemented"); + } + + public ushort ReadKStatus() + { + // if (Log.Enabled) Log.Write(LogType.Warning, LogComponent.ShugartControl, "<-KStatus read"); + + // "All status bits are inverted on the X bus because use of the comparable non-inverting drivers + // was forbidden when the board was designed." + ushort value = (ushort)~( + (_verifyError ? 0x0001 : 0x0000) | + (_crcError ? 0x0002 : 0x0000) | + (_overrun ? 0x0004 : 0x0000) | + (_writeFault ? 0x0008 : 0x0000) | + (!_drive.IsReady ? 0x0010 : 0x0000) | + (_sa1000 ? 0x0020 : 0x0000) | + (!_sectorFound ? 0x0040 : 0x0000) | + (_indexFound ? 0x0080 : 0x0000) | + (_firmwareEnable ? 0x0100 : 0x0000) | + (_drive.Track0 ? 0x0200 : 0x0000) | + (SeekComplete() ? 0x0400 : 0x0000) | + ((~_headSelect & 0x1f) << 11) + ); + + /* + if (Log.Enabled) Log.Write( + LogType.Verbose, + LogComponent.ShugartControl, + "0x{0:x4}<-KStatus", + value); */ + + return value; + } + + public ushort ReadKTest() + { + // + // This is only partially implemented, enough to indicate to the microcode what kind of drive is + // attached. + // + // Drive type is exposed via KTest[9] (Sector'). + // For an SA1000 this is always 0. + // For a Quantum Q2040, this is always 1. + // For a Quantum Q2080, this is the inverse of the MSB of the head select (KCtl[0]). + // + + int value = 0; + + switch (_drive.Type) + { + case DriveType.SA1004: + value = 0; + break; + + case DriveType.Q2040: + value = 0x40; + break; + + case DriveType.Q2080: + value = ((~_headSelect) & 0x10) == 0 ? 0x40 : 0; + break; + } + + return (ushort)value; + } + + public void SetKOData(ushort value) + { + if (_writePipeline.Count > 1) + { + if (Log.Enabled) Log.Write(LogType.Error, + LogComponent.ShugartControl, + "KOData<- : Not ready for write! (c/h/s/f {0}/{1}/{2}/{3})", + _drive.Cylinder, + _drive.Head, + _debugSector, + _debugField); + + } + else + { + _writePipeline.Enqueue(value); + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "KOData<- : 0x{0:x4} latched", value); + } + + if (_wakeupRequest == ServiceRequest.WriteWordNeeded) + { + _system.CP.SleepTask(TaskType.Disk); + } + + } + + public ushort ReadKIData() + { + if (!_readWordReady) + { + if (Log.Enabled) Log.Write(LogType.Error, + LogComponent.ShugartControl, + "<-KIData : Not ready for read! (c/h/s/f {0}/{1}/{2}/{3})", + _drive.Cylinder, + _drive.Head, + _debugSector, + _debugField); + _overrun = true; + } + + // + // Put the microcode back to sleep now that we've read the next word. + // + if (_wakeupRequest == ServiceRequest.ReadWordReady) + { + _system.CP.SleepTask(TaskType.Disk); + } + + _readWordReady = false; + + // if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "<-KIData Read 0x{0:x4} from c/h/w {1}/{2}/{3} (sector {4} field {5})", _readData, _drive.Cylinder, _drive.Head, _drive.WordIndex, _debugSector, _debugField); + + return (ushort)_readData; + } + + /// + /// Called by the drive to let the controller know that a new word is ready to be read or written. + /// + public void SignalDiskWordReady() + { + if (_drive.Index) + { + _indexFound = true; + + // Wake up if we're waiting for the Index + if (_wakeupRequest == ServiceRequest.IndexFound) + { + _system.CP.WakeTask(TaskType.Disk); + } + + // + // Reset debug counters + // + _debugSector = -1; + _debugField = -1; + } + + // Check for overrun on reads + // + if (_readWordReady && + _transferEnable && + _transfer == TransferType.Read && + _readState == ReadState.Data) + { + // No data available from microcode. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Read data: overrun."); + _overrun = true; + } + + _readWordReady = true; + _readData = _drive.ReadData(); + + // + // Check for Header Address Mark and set SectorFound bit if present. + if (_readData == _headerAddressMark) + { + _sectorFound = true; + + if (_debugSector != -1 && _debugField != 2) + { + //Console.WriteLine("Only found {0} fields on the last sector.", _debugField); + } + + _debugSector++; // next sector + _debugField = 0; // header field + } + + if (_readData == _labelDataAddressMark) + { + _debugField++; // next field + } + + // + // Run the transfer state machine + // + if (_transferEnable) + { + bool wake = false; + switch (_transfer) + { + case TransferType.Write: + switch (_writeState) + { + case WriteState.AutoPreamble: + // One word of preamble is written by the hardware itself. + _drive.WriteData(0); + + _transferCount++; + if (_transferCount > 1) + { + _transferCount = 0; + _writeState = WriteState.Preamble; + + // Wake the microcode task for the first preamble word + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Preamble word needed."); + } + break; + + case WriteState.Preamble: + // Four words of preamble are to be written by the microcode. + if (_writePipeline.Count > 0) + { + _drive.WriteData(_writePipeline.Dequeue()); + } + else + { + // No data available from microcode. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Write preamble: overrun."); + _overrun = true; + } + + // Wake the microcode task + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + + _transferCount++; + if (_transferCount > 4) + { + _transferCount = 0; + _writeState = WriteState.AddressMark; + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "AM word needed."); + } + else + { + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Preamble word needed."); + } + break; + + case WriteState.AddressMark: + // + // One word of address mark, written by the microcode. + // + ushort amWord = 0; + + if (_writePipeline.Count > 0) + { + amWord = _writePipeline.Dequeue(); + } + else + { + // No data available from microcode. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Write am data: overrun."); + _overrun = true; + } + + _drive.WriteAddressMark(amWord); + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "KOData<- : Address mark is 0x{0:x4}", amWord); + + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + _writeState = WriteState.Data; + break; + + case WriteState.Data: + // + // N words of data, written by the microcode. This state is left only when KCtl<- is set to enable + // writing the CRC. + // + if (_writeCRC) + { + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + _writeState = WriteState.CRC; + _transferCount = 0; + + // Write the first word of the CRC. + _drive.WriteCRC(0xbeef); + } + else + { + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + + _transferCount++; + + if (_writePipeline.Count > 0) + { + _drive.WriteData(_writePipeline.Dequeue()); + } + else + { + // No data available from microcode. + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Write data: overrun."); + _overrun = true; + } + + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Data word needed."); + } + break; + + case WriteState.CRC: + // + // One word of CRC, written by the hardware. This takes three word times, but + // only one word is written. + // We don't actually calculate the CRC (it cannot be read by microcode and + // we aren't simulating corrupted disks), but we write a known + // value for basic sanity checking on reads. + // + + // + // The actual CRC was written when we made the state transition. + // We write these other marker words to clobber anything that might + // be underneath them. + // + _drive.WriteCRC(0xdead); + + _transferCount++; + + // We still keep the microcode alive -- it may write words; we will ignore them. + wake = _wakeupRequest == ServiceRequest.WriteWordNeeded; + + if (_transferCount > 1) + { + _writeState = WriteState.Complete; + } + break; + + case WriteState.Complete: + // Nothing. + break; + + } + break; + + case TransferType.Verify: + switch (_verifyState) + { + case VerifyState.WaitForAddressMark: + // Check the current data under the read heads, if it's an address mark, + // wake the Disk task up and move to the Data state. + if (_readData == _headerAddressMark || + _readData == _labelDataAddressMark) + { + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Address Mark 0x{0:x4} found at word 0x{1:x4}, waking microcode.", + _readData, + _drive.WordIndex); + wake = true; + _verifyState = VerifyState.Data; + + // + // Set the CRC error flag: + // This is presumed true until all words of the field + // are read and can be compared with the CRC word at the + // end. + // + _crcError = true; + + if (_writePipeline.Count == 0) + { + // + // Hack: boot microcode does not "prime" the write pipeline -- + // relying on the hardware behavior of clearing the writeData buffer + // automatically; if the microcode hasn't written a word by this point + // enqueue a zero. + // + _writePipeline.Enqueue(0); + } + } + break; + + case VerifyState.Data: + // + // If this is a CRC, we check for our canard value and move to the CRC state, + // otherwise compare data. + // + ushort writeData = 0; + if (_writePipeline.Count > 0) + { + writeData = _writePipeline.Dequeue(); + } + + if ((_readData & 0x20000) != 0) + { + if (_readData != _fakeCRCValue) + { + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Verify CRC: 0x{0:x4} != 0x{1:x4}", _fakeCRCValue, _readData); + _crcError = true; + } + else + { + // + // All good. + // + _crcError = false; + } + + _verifyState = VerifyState.CRC; + } + else + { + if (writeData != _readData) + { + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Verify data: 0x{0:x4} != 0x{1:x4}", writeData, _readData); + _verifyError = true; + } + } + wake = true; + break; + + case VerifyState.CRC: + // + // We sit here forever. Microcode may send two extra words which we will ignore, + // after which it will disable the controller. + // + if (_writePipeline.Count > 0) + { + _writePipeline.Dequeue(); + } + wake = true; + break; + } + + break; + + case TransferType.Read: + switch (_readState) + { + case ReadState.WaitForAddressMark: + // Check the current data under the read heads, if it's an address mark, + // wake the Disk task up and move to the Data state. + if (_readData == _headerAddressMark || + _readData == _labelDataAddressMark) + { + if (Log.Enabled) Log.Write(LogComponent.ShugartControl, "Address Mark 0x{0:x4} found at word 0x{1:x4}, waking microcode.", + _drive.ReadData(), + _drive.WordIndex); + wake = true; + _readState = ReadState.Data; + } + break; + + case ReadState.Data: + // + // If this is a CRC, we check for our canard value and move to the CRC state, + // otherwise the microcode will read the next word via <-KIData + // + if ((_readData & 0x20000) != 0) + { + if (_readData != _fakeCRCValue) + { + if (Log.Enabled) Log.Write(LogType.Error, LogComponent.ShugartControl, "Read CRC: 0x{0:x4} != 0x{1:x4}", _fakeCRCValue, _readData); + _crcError = true; + } + + _readState = ReadState.CRC; + + } + wake = true; + break; + + case ReadState.CRC: + // + // We sit here forever. Microcode may send two extra words which we will ignore, + // after which it will disable the controller. + // + if (_writePipeline.Count > 0) + { + _writePipeline.Dequeue(); + } + wake = true; + break; + } + + break; + } + + // + // Wake up the disk task if we need the microcode to read or write a word. + // + if (wake) + { + _system.CP.WakeTask(TaskType.Disk); + } + + } + } + + public void SignalSeekComplete() + { + if (_wakeupRequest == ServiceRequest.SeekComplete && SeekComplete()) + { + _system.CP.WakeTask(TaskType.Disk); + } + } + + private bool SeekComplete() + { + // + // "[SeekComplete] is set when the drive is ready, it is selected, and the heads are not in motion." + // + return (_drive.IsReady && _driveSelect && _drive.SeekComplete); + } + + private void ResetTransfer() + { + _transfer = TransferType.None; + _writeState = WriteState.AutoPreamble; + _verifyState = VerifyState.WaitForAddressMark; + _readState = ReadState.WaitForAddressMark; + _transferCount = 0; + _writePipeline.Clear(); + } + + private DSystem _system; + + // + // The drive the controller is connected to. + // + private SA1000Drive _drive; + + // + // KCtl bits + // + private bool _writeEnable; + private int _wakeupControl; + private bool _writeCRC; + private bool _transferEnable; + private bool _firmwareEnable; + private bool _directionIn; + private bool _step; + private bool _reduceIW; + private bool _faultClear; + private bool _driveSelect; + private int _headSelect; + + private ServiceRequest _wakeupRequest; + + // + // KStatus bits + // + private bool _verifyError; + private bool _crcError; + private bool _overrun; + private bool _writeFault; + private bool _sa1000; + private bool _sectorFound; + private bool _indexFound; + + + // + // Read/Write state machine + // + private int _transferCount; + private uint _readData; + private bool _readWordReady; + + // + // Write pipeline (word being actively written to disk, word loaded by KOData<-. + // Kind of overkill to use a Queue object here. + // + private Queue _writePipeline = new Queue(); + + private WriteState _writeState; + private VerifyState _verifyState; + private ReadState _readState; + private TransferType _transfer; + + // + // Address marks + // + private const int _headerAddressMark = 0x1a141; + private const int _labelDataAddressMark = 0x1a143; + + // + // CRC (not actually CRC) value + // + private const int _fakeCRCValue = 0x2beef; + + // + // Debug metadata. Keep track of sector and field information. + // + private int _debugSector; + private int _debugField; + + private enum TransferType + { + None, + Read, + Write, + Verify + } + + private enum ServiceRequest + { + FirmwareEnable = 0, + SeekComplete = 1, + IndexFound = 2, // NB: HWRef has IndexFound and SectorFound values reversed. + SectorFound = 3, + ReadWordReady = 4, + WriteWordNeeded = 5, + NoWakeup0 = 6, + NoWakeup1 = 7, + } + + private enum WriteState + { + Invalid = 0, + AutoPreamble, // 2 words of zeros written by the hardware + Preamble, // 4 words of zeros written by the microcode + AddressMark, // 1 word of address mark written by the microcode + Data, // 256 words actual sector data written by the microcode + CRC, // 1 word of CRC, written by the hardware + Complete, // write done + } + + private enum VerifyState + { + Invalid = 0, + WaitForAddressMark, // Waiting for the next Address Mark to come around + Data, // Data is being read and compared with data from the microcode, by the hardware + CRC, // CRC is being checked by the hardware + } + + private enum ReadState + { + Invalid = 0, + WaitForAddressMark, // Waiting for the next Address Mark to come around + Data, // Data is being read + CRC, // CRC is being checked by the hardware + } + + } +} diff --git a/D/IOP/DMAController.cs b/D/IOP/DMAController.cs new file mode 100644 index 0000000..41b13bc --- /dev/null +++ b/D/IOP/DMAController.cs @@ -0,0 +1,452 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; + +namespace D.IOP +{ + public class DMAChannel + { + public DMAChannel() + { + Reset(); + Device = null; + } + + public void Reset() + { + Enabled = false; + Completed = false; + ChAddr = 0; + ChCount = -1; + Type = DMAType.Invalid; + } + + public bool Enabled; + + public bool Completed; + + // Channel address + public ushort ChAddr; + + // Channel data count + public int ChCount; + + // Channel DMA type + public DMAType Type; + + // DMA device + public IDMAInterface Device; + } + + public enum DMAType + { + Verify = 0, + Write = 1, + Read = 2, + Invalid = 3, + } + + /// + /// Defines an interface for DMA exchanges between devices and the DMA controller. + /// + public interface IDMAInterface + { + /// + /// DMA Request: device request to obtain a DMA cycle from the DMA controller + /// + bool DRQ { get; } + + /// + /// Writes a single byte to the device from the DMA controller + /// + /// + void DMAWrite(byte value); + + /// + /// Reads a single byte from the device to the DMA controller + /// + /// + byte DMARead(); + + /// + /// Indicates to a DMA device that a DMA transfer has completed. + /// + void DMAComplete(); + } + + /// + /// This implements the general behavior of the Intel 8257 as used in the Star. + /// It is far from a generic 8257 simulation, it could be made more general-purpose... + /// + public class DMAController : IIOPDevice + { + public DMAController(IOProcessor iop) + { + _iop = iop; + + for (int i = 0; i < _channels.Length; i++) + { + _channels[i] = new DMAChannel(); + } + } + + public void RegisterDevice(IDMAInterface device, int channel) + { + _channels[channel].Device = device; + } + + public DMAChannel GetChannel(int i) + { + return _channels[i]; + } + + /// + /// Hold request. Goes high when the DMA controller is taking control of the + /// bus (so the CPU should take a nap.) + /// + public bool HRQ + { + get { return _hrq; } + } + + /// + /// Terminal Count: TC is activated when the 14-bit value in the [last] selected channel's + /// terminal count register equals zero. + /// + public bool TC + { + get + { + if (_lastSelectedChannel != -1) + { + return _channels[_lastSelectedChannel].ChCount == 0; + } + else + { + return false; + } + } + + } + + public int[] ReadPorts + { + get { return _readPorts; } + } + + public int[] WritePorts + { + get { return _writePorts; } + } + + public void Reset() + { + _first = true; + + _rotatingPriority = false; + _extendedWrite = false; + _tcStop = false; + _autoLoad = false; + + _lastSelectedChannel = 0; + _nextToService = 0; + + for (int i = 0; i < _channels.Length; i++) + { + _channels[i].Reset(); + } + } + + /// + /// Executes a single DMA transfer (if there are + /// any transfers pending). This takes 4 clock + /// cycles. + /// + public void Execute() + { + // + // See if there's anything to do. + // + int nextChannel = SelectNextChannel(); + + // + // Raise HRQ if so. + // + _hrq = nextChannel != -1; + + if (_hrq) + { + DMAChannel c = _channels[nextChannel]; + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} selected. {1} bytes to {2}, addr 0x{3:x4}.", + nextChannel, c.ChCount, c.Type, c.ChAddr); + + switch (c.Type) + { + case DMAType.Verify: + throw new NotImplementedException("DMA Verify not implemented."); + + case DMAType.Read: + // Read byte from memory and transfer to device + byte dmaWrite = _iop.Memory.ReadByte(c.ChAddr); + c.Device.DMAWrite(dmaWrite); + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "DMA read transfer of byte 0x{0:x2} from address 0x{1:x4}", dmaWrite, c.ChAddr); + break; + + case DMAType.Write: + // Read byte from device and transfer to memory. + byte dmaRead = c.Device.DMARead(); + _iop.Memory.WriteByte(c.ChAddr, dmaRead); + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "DMA write transfer of byte 0x{0:x2} to address 0x{1:x4}", dmaRead, c.ChAddr); + break; + } + + // Increment address, decrement counter. + c.ChAddr++; + c.ChCount--; + + // If the counter runs out, stop the channel if so enabled. + if (c.ChCount == 0) + { + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} completed.", nextChannel); + // Stop this crazy thing. + if (_tcStop) + { + c.Enabled = false; + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} disabled.", nextChannel); + } + + c.Completed = true; + c.Device.DMAComplete(); + } + + _lastSelectedChannel = nextChannel; + } + } + + public void WritePort(int port, byte value) + { + switch ((DMAPorts)port) + { + case DMAPorts.DmaMode: + _first = true; + + _channels[0].Enabled = (value & 0x01) != 0; + _channels[1].Enabled = (value & 0x02) != 0; + _channels[2].Enabled = (value & 0x04) != 0; + _channels[3].Enabled = (value & 0x08) != 0; + + _rotatingPriority = (value & 0x10) != 0; + _extendedWrite = (value & 0x20) != 0; + _tcStop = (value & 0x40) != 0; + _autoLoad = (value & 0x80) != 0; + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "DMAMode: en: {0},{1},{2},{3} rp {4} ew {5} tc {6} al {7}", + _channels[0].Enabled, _channels[1].Enabled, _channels[2].Enabled, _channels[3].Enabled, + _rotatingPriority, _extendedWrite, _tcStop, _autoLoad); + + if (_autoLoad) + { + throw new NotImplementedException("AutoLoad not yet implemented."); + } + break; + + case DMAPorts.DmaCh0Addr: + case DMAPorts.DmaCh1Addr: + case DMAPorts.DmaCh2Addr: + case DMAPorts.DmaCh3Addr: + { + int ch = (port - 0xa0) / 2; + if (_first) + { + _channels[ch].ChAddr = value; + } + else + { + _channels[ch].ChAddr = (ushort)(_channels[ch].ChAddr | (value << 8)); + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} address set to 0x{1:x4}", ch, _channels[ch].ChAddr); + } + _first = !_first; + } + break; + + case DMAPorts.DmaCh0Count: + case DMAPorts.DmaCh1Count: + case DMAPorts.DmaCh2Count: + case DMAPorts.DmaCh3Count: + { + int ch = (port - 0xa1) / 2; + if (_first) + { + _channels[ch].ChCount = value; + } + else + { + // + 1 because the value loaded is the number of bytes-1. + _channels[ch].ChCount = (ushort)(_channels[ch].ChCount | ((value & 0x3f) << 8)) + 1; + _channels[ch].Type = (DMAType)(value >> 6); + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} count set to 0x{1:x4}", ch, _channels[ch].ChCount); + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "Channel {0} type set to {1}", ch, _channels[ch].Type); + } + _first = !_first; + } + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected write to port {0:x2}", port)); + } + } + + public byte ReadPort(int port) + { + byte value = 0; + + switch ((DMAPorts)port) + { + case DMAPorts.DmaStatus: + // "The low 4 bits of this register indicate what channels have completed." + value = (byte)((_channels[0].Completed ? 0x01 : 0x00) | + (_channels[1].Completed ? 0x02 : 0x00) | + (_channels[2].Completed ? 0x04 : 0x00) | + (_channels[3].Completed ? 0x08 : 0x00)); + + if (Log.Enabled) Log.Write(LogComponent.IOPDMA, "DMAStatus read {0}", value); + + // TC Status bits are cleared after the status register is read. + _channels[0].Completed = false; + _channels[1].Completed = false; + _channels[2].Completed = false; + _channels[3].Completed = false; + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected read from port {0:x2}", port)); + } + + return value; + + } + + private int SelectNextChannel() + { + int nextChannel = -1; + + if (!_rotatingPriority) + { + // + // Select the highest priority channel that has something to do. + // Channel 0 has the highest priority. + // + for (int i = 0; i < 4; i++) + { + if (_channels[i].Enabled && _channels[i].Device != null &&_channels[i].Device.DRQ) + { + nextChannel = i; + break; + } + } + } + else + { + // + // Select the next channel in the cycle, if it has something to do. + // + for (int i = 0; i < 4; i++) + { + int j = (i + _nextToService) % 4; + if (_channels[j].Enabled && _channels[j].Device != null && _channels[j].Device.DRQ) + { + nextChannel = j; + _nextToService = j + 1; + break; + } + } + } + + return nextChannel; + } + + private readonly int[] _readPorts = new int[] + { + (int)DMAPorts.DmaStatus, + }; + + private readonly int[] _writePorts = new int[] + { + (int)DMAPorts.DmaCh0Addr, + (int)DMAPorts.DmaCh0Count, + (int)DMAPorts.DmaCh1Addr, + (int)DMAPorts.DmaCh1Count, + (int)DMAPorts.DmaCh2Addr, + (int)DMAPorts.DmaCh2Count, + (int)DMAPorts.DmaCh3Addr, + (int)DMAPorts.DmaCh3Count, + (int)DMAPorts.DmaMode, + }; + + // Which address byte to store when loading registers. + private bool _first; + + private DMAChannel[] _channels = new DMAChannel[4]; + + // DMA Mode Flags + private bool _rotatingPriority; + private bool _extendedWrite; + private bool _tcStop; + private bool _autoLoad; + + // Scheduling + private int _nextToService; + private int _lastSelectedChannel; + + private IOProcessor _iop; + + private enum DMAPorts + { + DmaCh0Addr = 0xa0, + DmaCh0Count = 0xa1, + DmaCh1Addr = 0xa2, + DmaCh1Count = 0xa3, + DmaCh2Addr = 0xa4, + DmaCh2Count = 0xa5, + DmaCh3Addr = 0xa6, + DmaCh3Count = 0xa7, + DmaMode = 0xa8, // Write + DmaStatus = 0xa8, // Read + } + + private bool _hrq; + } +} diff --git a/D/IOP/FloppyController.cs b/D/IOP/FloppyController.cs new file mode 100644 index 0000000..2f7a97c --- /dev/null +++ b/D/IOP/FloppyController.cs @@ -0,0 +1,1039 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; + +namespace D.IOP +{ + /// + /// This implements the WD FD1797 controller and the IOP's external floppy state registers. + /// It implements the IDMAInterface so that DMA transfers can take place. + /// + /// TODO: This needs a lot of cleanup and refinement. In particular: + /// - Write support needs to be added + /// - Need enforcing of sector formats (if controller is set up to read a double density sector, + /// and a single-density sector is read, something bad needs to happen, rather than nothing.) + /// + public class FloppyController : IIOPDevice, IDMAInterface + { + public FloppyController(FloppyDrive drive, DSystem system) + { + _system = system; + _drive = drive; + } + + public FloppyDrive Drive + { + get { return _drive; } + } + + public int[] ReadPorts + { + get { return _readPorts; } + } + + public int[] WritePorts + { + get { return _writePorts; } + } + + public bool Interrupt + { + get { return _interruptPending; } + } + + // + // IDMAInterface methods and properties + // + public bool DRQ + { + get + { + if (_drq) + { + _drqCounter--; + + if (_drqCounter == 0) + { + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + } + + public byte DMARead() + { + if (!_drq) + { + throw new InvalidOperationException("Unexpected DMA read with DRQ low."); + } + + // + // We cheat (as with reading the Data register): rather than emulating the timing of data moving + // past the floppy drive heads, since we read the entire sector in at once + // we assume the data's always ready so we keep DRQ high until the data has all been read. + // This means we won't be simulating DATA LATE errors. + // + // Return the next byte, if any. + // Keep DRQ raised until all bytes in the buffer have been read. + // + byte dmaRead = _sectorBuffer[_sectorDataIndex]; + + _sectorDataIndex++; + if (_sectorDataIndex > _sectorBuffer.Length - 1) + { + FinishDataTransfer(); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "DMA sector read completed."); + } + else + { + _drqCounter = 16; + } + + return dmaRead; + } + + public void DMAWrite(byte value) + { + throw new NotImplementedException("DMA write not implemented yet."); + } + + public void DMAComplete() + { + + } + + public void WritePort(int port, byte value) + { + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC port {0} write {1:x2}", (FDCPorts)port, value); + + switch ((FDCPorts)port) + { + case FDCPorts.ExtFDCState: + _extState = (FDCStateFlags)value; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC Ext state {0} ({1:x2})", (FDCStateFlags)value, value); + + _drive.DriveSelect = (_extState & FDCStateFlags.DriveSelect) != 0; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Drive selected: {0}", _drive.DriveSelect); + + // Enable or disable the FDC chip + if ((_extState & FDCStateFlags.EnableFDC) != 0) + { + EnableFDC(); + } + else + { + DisableFDC(); + } + break; + + case FDCPorts.FDCTrack: + _fdcTrack = value; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC track {0}", value); + break; + + case FDCPorts.FDCSector: + _fdcSector = value; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC sector {0}", value); + break; + + case FDCPorts.FDCData: + _fdcData = value; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC data 0x{0:x}", value); + break; + + case FDCPorts.FDCCommand: + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC command 0x{0:x}", value); + DoCommand(value); + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected write to port {0:x2}", port)); + } + } + + public byte ReadPort(int port) + { + byte value = 0; + + switch ((FDCPorts)port) + { + case FDCPorts.FDCTrack: + value = _fdcTrack; + break; + + case FDCPorts.FDCSector: + value = _fdcSector; + break; + + case FDCPorts.FDCData: + if (_drq) + { + // There's a read pending, read the next byte in if any. + if (_sectorDataIndex < _sectorBuffer.Length - 1) + { + _fdcData = _sectorBuffer[_sectorDataIndex]; + _sectorDataIndex++; + + if (_sectorDataIndex > _sectorBuffer.Length - 1) + { + FinishDataTransfer(); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Polled sector read completed."); + } + } + } + + value = _fdcData; + break; + + case FDCPorts.FDCStatus: + value = ReadStatus(); + break; + + case FDCPorts.ExtFDCStatusReg: + value = ReadExtStatus(); + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Floppy ext status read {0}", (FDCStatusFlags)value); + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected read from port {0:x2}", port)); + } + + return value; + + } + + public void Reset() + { + _extState = FDCStateFlags.None; + _lastCommand = FDCCommand.Restore; + _fdcEnabled = false; + _sectorDataIndex = 0; + _drqCounter = 16; + _indexReset = false; + + _drive.Reset(); + + ResetFlags(); + } + + private void ResetFlags() + { + _fdcData = 0; + _fdcSector = 0; + _fdcTrack = 0; + _commandAbort = false; + + _crcError = false; + _busy = false; + _headLoaded = false; + _seekError = false; + _recordTypeWriteFault = false; + _rnf = false; + _lostData = false; + _drq = false; + + ClearInterrupt(); + } + + // + // Returns the status given the last command executed. + // + private byte ReadStatus() + { + // Interrupt signal is reset when the status register is read. + ClearInterrupt(); + + byte value = 0; + + switch(_lastCommand) + { + // Type I Commands + case FDCCommand.Restore: + case FDCCommand.Seek: + case FDCCommand.StepNoUpdate: + case FDCCommand.StepUpdate: + case FDCCommand.StepInNoUpdate: + case FDCCommand.StepInUpdate: + case FDCCommand.StepOutNoUpdate: + case FDCCommand.StepOutUpdate: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (WriteProtect() ? 0x40 : 0x00) | + (_headLoaded ? 0x20 : 0x00) | + (_seekError ? 0x10 : 0x00) | + (_crcError ? 0x08 : 0x00) | + (Track0() ? 0x04 : 0x00) | + (Index() ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + + // Type II/III Commands + case FDCCommand.ReadAddress: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (_rnf ? 0x10 : 0x00) | + (_crcError ? 0x08 : 0x00) | + (_lostData ? 0x04 : 0x00) | + (_drq ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + + case FDCCommand.ReadTrack: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (_lostData ? 0x04 : 0x00) | + (_drq ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + + case FDCCommand.ReadSectorMultiple: + case FDCCommand.ReadSectorSingle: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (_recordTypeWriteFault ? 0x20 : 0x00) | + (_rnf ? 0x10 : 0x00) | + (_crcError ? 0x08 : 0x00) | + (_lostData ? 0x04 : 0x00) | + (_drq ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + + case FDCCommand.WriteSectorMultiple: + case FDCCommand.WriteSectorSingle: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (WriteProtect() ? 0x40 : 0x00) | + (_recordTypeWriteFault ? 0x20 : 0x00) | + (_rnf ? 0x10 : 0x00) | + (_crcError ? 0x08 : 0x00) | + (_lostData ? 0x04 : 0x00) | + (_drq ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + + case FDCCommand.WriteTrack: + value = + (byte)((NotReady() ? 0x80 : 0x00) | + (WriteProtect() ? 0x40 : 0x00) | + (_recordTypeWriteFault ? 0x20 : 0x00) | + (_lostData ? 0x04 : 0x00) | + (_drq ? 0x02 : 0x00) | + (_busy ? 0x01 : 0x00)); + break; + } + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC status read {0:x} ({1})", value, (Type1Status)value); + + return value; + } + + /// + /// Reads the IOP's external floppy status register + /// + private byte ReadExtStatus() + { + bool doubleSided = false; + bool sa800 = false; + bool diskChange = false; + + if (_drive.DriveSelect) + { + doubleSided = !_drive.IsSingleSided; + sa800 = !_drive.IsLoaded; + diskChange = _drive.DiskChange; + } + + byte extStatus = (byte)( + (diskChange ? 0x80 : 0x00) | + (_system.IOP.DMAController.TC ? 0x40 : 0x00) | // end count + (doubleSided ? 0x20 : 0x00) | + (sa800 ? 0x10 : 0x00)); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Ext Status 0x{0:x2}", extStatus); + + return extStatus; + } + + private void EnableFDC() + { + if (_fdcEnabled) + { + // Already enabled, no need to do anything. + return; + } + + // + // After enabling the FDC, the FDC comes out of reset. + // Technically this takes about 12ms, we're just + // doing it immediately. + // + _fdcEnabled = true; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC enabled."); + + // "A logic low on the [-MR] input resets the device and loads HEX 03 + // into the command register. The Not Ready (status bit 7) is reset + // during -MR ACTIVE. When -MR is brought to a logic high, a RESTORE + // command is executed, regardless of the state of the Ready signal + // from the drive. Also, HEX 01 is loaded into the sector register." + + // Send the RESTORE command. + DoCommand((byte)FDCCommand.Restore << 4); + + // + // If DriveSelect is high, we will set INDEX to high and schedule an event + // to reset it after a short duration. This makes FDCTest happy, and + // is undocumented in the WD spec. + // + if (_drive.DriveSelect) + { + _indexReset = true; + + _system.Scheduler.Schedule(_resetIndexDuration, + (timestampNsec, context) => + { + // Reset the index signal now. + _indexReset = false; + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Resetting INDEX signal after FDC reset."); + }); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Setting INDEX signal after FDC reset."); + } + } + + private void DisableFDC() + { + if (!_fdcEnabled) + { + // Already disabled, no need to do anything. + return; + } + + _fdcEnabled = false; + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC disabled. Resetting FDC."); + + // + // "A logic low on the [-MR] input resets the device and loads HEX 03 + // into the command register. The Not Ready (status bit 7) is reset + // during -MR ACTIVE." + // + // TODO: + // HEX 03 only affects the step rate, which I'm not particularly concerned + // about at the moment. + // + ResetFlags(); + _extState = FDCStateFlags.None; + + _lastCommand = FDCCommand.Restore; + } + + private void DoCommand(int commandData) + { + // Interrupt signal is reset when the command register is written. + ClearInterrupt(); + + _commandAbort = false; + FDCCommand command = (FDCCommand)(commandData >> 4); + + // Save the last command so we know what status register set to access; + // ForceInterrupt is the exception to this rule. + if (command != FDCCommand.ForceInterrupt) + { + if (_busy) + { + // Early abort if a command is already in progress. + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "FDC busy, seek aborted.", _stepDirection); + return; + } + + _lastCommand = command; + } + + int data = commandData & 0x1f; + + switch(command) + { + case FDCCommand.Restore: + // Seek inward until the Track 0 sensor trips. + // We use the general Seek mechanism, which we + // cheat by loading the FDC track register with the + // actual physical head position, so a seek to 0 + // will actually end up at physical track 0. + _fdcTrack = (byte)_drive.Track; + Seek(0, new Type1CommandParams(data)); + break; + + case FDCCommand.Seek: + // + // "This command assumes that the Track Register contains the track number + // of the current position of the Read-Write head and the Data Register contains + // the desired track number." + // + Seek(_fdcData, new Type1CommandParams(data)); + break; + + case FDCCommand.StepNoUpdate: + case FDCCommand.StepUpdate: + Step(StepDirection.Last, new Type1CommandParams(data)); + break; + + case FDCCommand.StepInNoUpdate: + case FDCCommand.StepInUpdate: + Step(StepDirection.In, new Type1CommandParams(data)); + break; + + case FDCCommand.StepOutNoUpdate: + case FDCCommand.StepOutUpdate: + Step(StepDirection.Out, new Type1CommandParams(data)); + break; + + case FDCCommand.ReadSectorSingle: + ReadSector(new Type2CommandParams(data)); + break; + + case FDCCommand.ForceInterrupt: + ForceInterrupt(data); + break; + + default: + throw new NotImplementedException( + String.Format("FDC command {0} not implemented.", command)); + } + } + + private void Seek(int track, Type1CommandParams p) + { + _seekDestination = track; + _seekError = false; + + // Schedule the first step of the heads + _system.Scheduler.Schedule(_commandBeginNsec, p, SeekCallback); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Seek to {0} initialized.", track); + } + + private void Step(StepDirection direction, Type1CommandParams p) + { + if (direction != StepDirection.Last) + { + _stepDirection = direction; + } + + _seekError = false; + + // Schedule a step of the heads. + if (!_busy) + { + _system.Scheduler.Schedule( + _commandBeginNsec, + (timestampNsec, context) => + { + if (_commandAbort) + { + // + // Abort the step, perform no head steps and update no registers. + // + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Step aborted by ForceInterrupt."); + return; + } + + if (!_busy) + { + // Raise the busy flag -- the command has been accepted. + _busy = true; + + // Schedule the actual step + _system.Scheduler.Schedule( + _stepTimeNsec, + (ts, ctx) => + { + switch (_stepDirection) + { + case StepDirection.Out: // one track closer to the outer edge + _drive.SeekTo(_drive.Track - 1); + + if (p.Update) + { + _fdcTrack--; + } + break; + + case StepDirection.In: // one track closer to the inner edge + _drive.SeekTo(_drive.Track + 1); + + if (p.Update) + { + _fdcTrack++; + } + break; + + default: + throw new InvalidOperationException("Unepxected step type."); + } + + if (p.Verify && _drive.IsLoaded && (_fdcTrack != _drive.Track)) + { + _seekError = true; + } + + _headLoaded = p.HeadLoad; + + RaiseInterrupt(); + + // + // Reset the busy flag, the step is complete. + // + _busy = false; + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Step to {0} (physical {1}) completed.", _seekDestination, _drive.Track); + }); + } + }); + } + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Step {0} initialized.", _stepDirection); + } + + private void ReadSector(Type2CommandParams p) + { + // Schedule the read. + _system.Scheduler.Schedule( + _commandBeginNsec, + (timestampNsec, context) => + { + // + // Set RNF if specified sector is out of range or if the drive's physical + // head position != the FDC's track register. + // + int sectorCount = _drive.Disk.GetTrack(_drive.Track, p.SideSelect ? 1 : 0).SectorCount; + _rnf = (_fdcTrack != _drive.Track) || + (_fdcSector > sectorCount); + + // + // Read the sector into the sector buffer. + // BUSY remains active until the transfer is complete -- whenever DMA or a polled + // read finishes the buffer off. + // + if (!NotReady() && !_rnf) + { + // the FDC's sector value is 1-indexed. + _sectorBuffer = _drive.Disk.GetSector(_drive.Track, p.SideSelect ? 1 : 0, _fdcSector - 1).Data; + _sectorDataIndex = 0; + _busy = true; + _drq = true; + _drqCounter = 16; + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Sector read of C/H/S {0}/{1}/{2} initialized.", + _fdcTrack, p.SideSelect ? 1 : 0, _fdcSector); + } + else + { + _sectorBuffer = null; + _busy = false; + } + }); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Read initialized."); + } + + private void ForceInterrupt(int flags) + { + // + // Terminate any pending command. + // and generate an interrupt for the condition specified in + // i0-i3. + // This is only used in FDCTest.asm and doesn't actually use + // the interrupt facilities + + // Clear the BUSY flag + _busy = false; + + // Cause any pending command to abort before completing. + _commandAbort = true; + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Force Interrupt 0x{0:x2}", flags); + } + + private void SeekCallback(ulong skewNsec, object context) + { + if (_commandAbort) + { + // + // Abort the seek, perform no head steps and update no registers. + // + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Seek aborted by ForceInterrupt."); + return; + } + + // Set the FDC busy status. + _busy = true; + + if (_fdcTrack == _seekDestination) + { + // We've arrived. Everyone out of the car. + // Clear the FDC Type I BUSY status + _busy = false; + + // + // Do a verification. Since we're not emulating the floppy at a level + // low enough to verify header CRC and sector IDs, we assume those are OK + // and only compare our Physical track counter with the FDC's track counter. + // + Type1CommandParams p = (Type1CommandParams)context; + if (p.Verify && _drive.IsLoaded && (_fdcTrack != _drive.Track)) + { + _seekError = true; + } + + _headLoaded = p.HeadLoad; + + RaiseInterrupt(); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Seek to {0} (physical {1}) completed.", _seekDestination, _drive.Track); + } + else + { + // Not there yet, move one step in the right direction. + if (_fdcTrack < _seekDestination) + { + _fdcTrack++; + _drive.SeekTo(_drive.Track + 1); + } + else + { + _fdcTrack--; + _drive.SeekTo(_drive.Track - 1); + } + + _system.Scheduler.Schedule(_stepTimeNsec, context, SeekCallback); + + if (Log.Enabled) Log.Write(LogComponent.IOPFloppy, "Seek step to {0} (physical {1})", _fdcTrack, _drive.Track); + + } + } + + /// + /// Invoked when a sector data transfer completes (either by DMA or programmed I/O). + /// + private void FinishDataTransfer() + { + _drq = false; + _busy = false; + _sectorBuffer = null; + _sectorDataIndex = 0; + + RaiseInterrupt(); + } + + private void ClearInterrupt() + { + _interruptPending = false; + // RST7.5 is edge triggered, no need to clear it here. + } + + private void RaiseInterrupt() + { + _interruptPending = true; + _system.IOP.CPU.RaiseExternalInterrupt(InterruptType.RST7_5); + } + + private bool NotReady() + { + return _drive.DriveSelect ? !_drive.IsLoaded : true; + } + + private bool WriteProtect() + { + return _drive.IsLoaded && _drive.IsWriteProtected; + } + + private bool Track0() + { + return _drive.Track0; + } + + private bool Index() + { + return _drive.Index || _indexReset; + } + + private readonly int[] _readPorts = new int[] + { + (int)FDCPorts.FDCStatus, + (int)FDCPorts.FDCTrack, + (int)FDCPorts.FDCSector, + (int)FDCPorts.FDCData, + (int)FDCPorts.ExtFDCStatusReg, + }; + + private readonly int[] _writePorts = new int[] + { + (int)FDCPorts.FDCCommand, + (int)FDCPorts.FDCTrack, + (int)FDCPorts.FDCSector, + (int)FDCPorts.FDCData, + (int)FDCPorts.ExtFDCState + }; + + + // FDC data + private byte _fdcTrack; + private byte _fdcSector; + private byte _fdcData; + + // Sector data + private byte[] _sectorBuffer; + private int _sectorDataIndex; + + // Status flags + // Type 1/2/3: + private bool _crcError; // 0x08 + private bool _busy; // 0x01 + + // Type 1: + private bool _headLoaded; // 0x20 + private bool _seekError; // 0x10 + + // Type 2/3: + private bool _recordTypeWriteFault; // 0x20 + private bool _rnf; // 0x10 + private bool _lostData; // 0x04 + private bool _drq; // 0x02 + + private int _drqCounter; + + // Overrides drive Index signal immediately after FDC reset. + private bool _indexReset; + + /// + /// Current state of the enabled signal for the FDC. + /// + private bool _fdcEnabled; + + // Interrupt flag. This is set when a command completes, + // reset when the status register is read or the command + // register is written to. + private bool _interruptPending; + + private FDCCommand _lastCommand; + + // External state (written with ExtFDCState port) + private FDCStateFlags _extState; + + // Seek state + private int _seekDestination; + private ulong _commandBeginNsec = 12 * Conversion.UsecToNsec; + private ulong _stepTimeNsec = 6 * Conversion.MsecToNsec; + + // Step state + private StepDirection _stepDirection; + + + // + // This exists because the FDCTest code expects undocumented behavior from the FDC chip. + // Specifically, after the FDC is reset, INDEX goes high as long as a drive is selected + // (regardless of whether a disk is in the drive or not). + // After a short (unspecified) interval it goes low again. + // On a reset with drive selected, we will kick off an event to do what the test expects. + // + private ulong _resetIndexDuration = 10 * Conversion.MsecToNsec; // Guesswork + + // + // Command execution state + // + private bool _commandAbort; + + // + // The floppy drive we're talking to + // + private FloppyDrive _drive; + + // + // The system we belong to + // + private DSystem _system; + + + private enum FDCPorts + { + FDCCommand = 0x84, // Commands (write) + FDCStatus = 0x84, // Status (read) + FDCTrack = 0x85, // Track register (r/w) + FDCSector = 0x86, // Sector register (r/w) + FDCData = 0x87, // Data register (r/w) + + // These are external to the actual FDC chip + ExtFDCStatusReg = 0xe8, // External status register (read) + ExtFDCState = 0xe8, // External state register (write) + } + + /// + /// Flags for FDCState port + /// + [Flags] + private enum FDCStateFlags + { + EnableWaits = 0x80, // Set wait cycles in FDCState (what does this mean?) + Precomp = 0x40, // Apparently the IOP handles write precomp itself? (FDC chip can do this...) + Side = 0x20, // Number of sides? + Density = 0x08, // Connected to DDEN on FDC ( 0 = double, 1 = single) + EnableFDC = 0x04, // Enables the FDC chip + DriveSelect = 0x01, // enables drive select ( 0 = no drive selected, 1 = drive selected) + None = 0x00, + } + + /// + /// Flags for FDCStatusReg port + /// From BootDefs.asm + /// + [Flags] + private enum FDCStatusFlags + { + IntMask = 0x80, // FDC interrupt request status + EndCount = 0x40, // FDC end count + TwoSided = 0x20, // FDC two-sided bit + SA800 = 0x10, // "now no-floppy bit" (presumably set when no floppy is inserted in the drive) + None = 0x00, + } + + private enum FDCCommand + { + Restore = 0, + Seek = 1, + StepNoUpdate = 2, + StepUpdate = 3, + StepInNoUpdate = 4, + StepInUpdate = 5, + StepOutNoUpdate = 6, + StepOutUpdate = 7, + ReadSectorSingle = 8, + ReadSectorMultiple = 9, + WriteSectorSingle = 0xa, + WriteSectorMultiple = 0xb, + ReadAddress = 0xc, + ForceInterrupt = 0x0d, + ReadTrack = 0xe, + WriteTrack = 0xf, + } + + private enum StepDirection + { + In = 0, + Out = 1, + Last = 2, + } + + /// + /// Encapsulates Type 1 command options. + /// + private struct Type1CommandParams + { + public Type1CommandParams(bool update, bool headLoad, bool verify) + { + Update = update; + HeadLoad = headLoad; + Verify = verify; + } + + public Type1CommandParams(int p) + { + Update = (p & 0x10) != 0; + HeadLoad = (p & 0x08) != 0; + Verify = (p & 0x04) != 0; + } + + public bool Update; + public bool HeadLoad; + public bool Verify; + } + + /// + /// Encapsulates Type 2 and 3 command options + /// + private struct Type2CommandParams + { + public Type2CommandParams(int p) + { + SectorLength = ((p & 0x8) != 0); + Delay = ((p & 0x4) != 0); + SideSelect = ((p & 0x2) != 0); + DataAddressMark = ((p & 0x1) != 0); + } + + public bool SideSelect; + public bool Delay; + public bool SectorLength; + public bool DataAddressMark; + } + + // + // WD FD179X status register bits + // + [Flags] + private enum Type1Status + { + NotReady = 0x80, + WriteProtect = 0x40, + HeadLoaded = 0x20, + SeekError = 0x10, + CRCError = 0x08, + Track0 = 0x04, + Index = 0x02, + Busy = 0x01, + } + + /// + /// Actually for type 2 or 3 + /// + [Flags] + private enum Type2Status + { + NotReady = 0x80, + WriteProtect = 0x40, + RecordTypeWriteFault = 0x20, + RNF = 0x10, + CRCError = 0x08, + LostData = 0x04, + DRQ = 0x02, + Busy = 0x01, + } + } +} diff --git a/D/IOP/I8085IOBus.cs b/D/IOP/I8085IOBus.cs new file mode 100644 index 0000000..0eb444a --- /dev/null +++ b/D/IOP/I8085IOBus.cs @@ -0,0 +1,37 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace D.IOP +{ + public interface I8085IOBus + { + void Out(byte port, byte o); + + byte In(byte port); + } +} diff --git a/D/IOP/I8085MemoryBus.cs b/D/IOP/I8085MemoryBus.cs new file mode 100644 index 0000000..ee6416e --- /dev/null +++ b/D/IOP/I8085MemoryBus.cs @@ -0,0 +1,42 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.IOP +{ + public interface I8085MemoryBus + { + byte ReadByte(ushort address); + + void WriteByte(ushort address, byte b); + + ushort ReadWord(ushort address); + + void WriteWord(ushort address, ushort w); + } +} diff --git a/D/IOP/IIOPDevice.cs b/D/IOP/IIOPDevice.cs new file mode 100644 index 0000000..224bea1 --- /dev/null +++ b/D/IOP/IIOPDevice.cs @@ -0,0 +1,41 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.IOP +{ + public interface IIOPDevice + { + int[] ReadPorts { get; } + int[] WritePorts { get; } + + void WritePort(int port, byte value); + + byte ReadPort(int port); + } +} diff --git a/D/IOP/IOPIOBus.cs b/D/IOP/IOPIOBus.cs new file mode 100644 index 0000000..fd5deaf --- /dev/null +++ b/D/IOP/IOPIOBus.cs @@ -0,0 +1,95 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; + +namespace D.IOP +{ + public class IOPIOBus : I8085IOBus + { + public IOPIOBus() + { + _writeDispatch = new IIOPDevice[256]; + _readDispatch = new IIOPDevice[256]; + } + + public void RegisterDevice(IIOPDevice device) + { + foreach(byte port in device.ReadPorts) + { + if (_readDispatch[port] != null) + { + throw new InvalidOperationException(String.Format("Read port collision {0:x2} when adding device {1}", port, device)); + } + + _readDispatch[port] = device; + } + + foreach (byte port in device.WritePorts) + { + if (_writeDispatch[port] != null) + { + throw new InvalidOperationException(String.Format("Write port collision {0:x2} when adding device {1}", port, device)); + } + + _writeDispatch[port] = device; + } + } + + public void Out(byte port, byte val) + { + if (_writeDispatch[port] != null) + { + _writeDispatch[port].WritePort(port, val); + } + else + { + if (Log.Enabled) Log.Write(LogComponent.IOPIO, "Unhandled write to IO port ${0:x2}, ${1:x2}", port, val); + } + } + + public byte In(byte port) + { + if (_readDispatch[port] != null) + { + return _readDispatch[port].ReadPort(port); + } + else + { + if (Log.Enabled) Log.Write(LogComponent.IOPIO, "Unhandled read from IO port ${0:x2}", port); + return 0x00; + } + } + + + private IIOPDevice[] _writeDispatch; + private IIOPDevice[] _readDispatch; + } +} diff --git a/D/IOP/IOPMemoryBus.cs b/D/IOP/IOPMemoryBus.cs new file mode 100644 index 0000000..7ddda78 --- /dev/null +++ b/D/IOP/IOPMemoryBus.cs @@ -0,0 +1,224 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; +using System.IO; + +namespace D.IOP +{ + /// + /// Implements the IOP's memory bus. Memory map looks like + /// (see SysDefs.asm): + /// + /// $0000 - $1FFF : PROM (8K) + /// $2000 - $5FFF : RAM (16K) + /// $80B0 - $80BF : Host Addr PROM (16 bytes) + /// + /// + public class IOPMemoryBus : I8085MemoryBus + { + public IOPMemoryBus(I8085IOBus ioBus) + { + // 8K ROM + _rom = new byte[0x2000]; + + // 16K RAM. We actually allocate 24K + // to make addressing simpler. + _ram = new byte[0x6000]; + + // The 8085's IO ports are also memory-mapped at + // $8000 + port. + _io = ioBus; + + LoadPROMs(); + + LoadHostIDProm(); + } + + public byte ReadByte(ushort address) + { + if (address < 0x2000) + { + return _rom[address]; + } + else if(address < 0x6000) + { + return _ram[address]; + } + else if(address > 0x80af && address < 0x80c0) + { + // + // Host Address PROM. This contains 12 nybbles of data containing the 48-bit + // Ethernet host address and a checksum. + // The PROM itself is 16 bytes in size. + // + return _hostIdProm[address - 0x80b0]; + } + else if(address > 0x8000 && address < 0x8100) + { + // Memory-mapped I/O ports + if (Log.Enabled) Log.Write(LogComponent.IOPMemory, "Memory-mapped I/O read from {0:x4}", address); + return _io.In((byte)address); + } + else + { + // Nothing mapped here. + if (Log.Enabled) Log.Write(LogComponent.IOPMemory, "Read from nonexistent memory at {0:x4}", address); + return 0xff; + } + } + + public void WriteByte(ushort address, byte b) + { + if (address < 0x2000) + { + // ROM, not writeable. + } + else if (address < 0x6000) + { + _ram[address] = b; + } + else if (address > 0x8000 && address < 0x8100) + { + // Memory-mapped I/O ports + if (Log.Enabled) Log.Write(LogComponent.IOPMemory, "Memory-mapped I/O write at {0:x4}", address); + _io.Out((byte)address, b); + } + else + { + // Nothing mapped here. + if (Log.Enabled) Log.Write(LogComponent.IOPMemory, "Write to nonexistent memory at {0:x4}", address); + } + } + + public ushort ReadWord(ushort address) + { + return (ushort)(ReadByte(address) | (ReadByte((ushort)(address + 1)) << 8)); + } + + public void WriteWord(ushort address, ushort u) + { + WriteByte(address++, (byte)u); + WriteByte(address, (byte)(u >> 8)); + } + + public void UpdateHostIDProm() + { + LoadHostIDProm(); + } + + private void LoadPROMs() + { + // Loads PROMs into ROM space. The files for rev 3.1 are: + // U129 - 537P03029 - $0000 + // U130 - 537P03030 - $0800 + // U131 - 537P03700 - $1000 + // U132 - 537P03032 - $1800 + + LoadPROM("537P03029.bin", 0x0000); + LoadPROM("537P03030.bin", 0x0800); + LoadPROM("537P03700.bin", 0x1000); + LoadPROM("537P03032.bin", 0x1800); + } + + private void LoadPROM(string promName, ushort address) + { + string promPath = Path.Combine("IOP", "PROM", promName); + + using (FileStream promStream = new FileStream(promPath, FileMode.Open, FileAccess.Read)) + { + if (promStream.Length != 0x800) + { + throw new InvalidOperationException( + String.Format("PROM file {0} has unexpected size 0x{1:x}", promName, promStream.Length)); + } + + promStream.Read(_rom, address, 0x800); + } + } + + private void LoadHostIDProm() + { + // + // Copy data from the current emulator config into the HostID prom array. + // + for (int i = 0; i < 6; i++) + { + byte val = (byte)(Configuration.HostID >> (5 - i) * 8); + SetIDPromByte(i, val); + } + + // + // Calculate the checksum of the PROM. Looking at the PROM as + // 8 bytes (big-endian) rather than 16 nibbles, this is a cyclic XOR of: + // Bytes 0 & 1 - XOR'd together + // Bytes 2-5 + // Checksum is stored in Byte 6, complemented checksum is in Byte 7. + // Byte 8 appears to be unused. + // + byte checksum = RotateLeft((byte)(GetIDPromByte(0) ^ GetIDPromByte(1))); + + for (int i = 2; i < 6; i++) + { + checksum ^= GetIDPromByte(i); + checksum = RotateLeft(checksum); + } + + SetIDPromByte(6, checksum); + SetIDPromByte(7, (byte)(~checksum)); + } + + private byte GetIDPromByte(int byteNumber) + { + return (byte)((_hostIdProm[byteNumber * 2] & 0xf) | (_hostIdProm[byteNumber * 2 + 1] << 4)); + } + + private void SetIDPromByte(int byteNumber, byte value) + { + _hostIdProm[byteNumber * 2] = (byte)(value & 0xf); + _hostIdProm[byteNumber * 2 + 1] = (byte)(value >> 4); + } + + private byte RotateLeft(byte value) + { + return (byte)((value << 1) | ((value & 0x80) != 0 ? 1 : 0)); + } + + private byte[] _rom; + private byte[] _ram; + + // + // Host ID Prom: contains the host's Ethernet MAC + checksum. + // 16 nybbles long. + private byte[] _hostIdProm = new byte[16]; + + private I8085IOBus _io; + } +} diff --git a/D/IOP/IOProcessor.cs b/D/IOP/IOProcessor.cs new file mode 100644 index 0000000..8a2f940 --- /dev/null +++ b/D/IOP/IOProcessor.cs @@ -0,0 +1,165 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace D.IOP +{ + /// + /// Encapsulates the entirety of the IOP hardware. + /// + public class IOProcessor + { + public IOProcessor(DSystem system) + { + _system = system; + _io = new IOPIOBus(); + _mem = new IOPMemoryBus(_io); + _cpu = new i8085(_mem, _io); + _keyboard = new Keyboard(); + _mouse = new Mouse(); + + // + // 8" floppy drive used by the IOP + // + _floppyDrive = new FloppyDrive(_system); + + // + // Add devices to the IO bus + // + _miscIO = new MiscIO(this); + _floppyController = new FloppyController(_floppyDrive, _system); + _dma = new DMAController(this); + _tty = new Printer(); + + // + // Register DMA devices with controller + // + _dma.RegisterDevice(_floppyController, 0); // Floppy, DMA Channel 0 + _dma.RegisterDevice(_system.CP, 1); // CP, DMA Channel 1 + + _io.RegisterDevice(_miscIO); + _io.RegisterDevice(_floppyController); + _io.RegisterDevice(_dma); + _io.RegisterDevice(_system.CP); + //_io.RegisterDevice(_tty); + + Reset(); + } + + public void Reset() + { + _cpu.Reset(); + _miscIO.Reset(); + _dma.Reset(); + _floppyController.Reset(); + } + + /// + /// Executes a single 8085 instruction or runs the DMA controller if DMA is in progress, + /// and returns the number of 3Mhz clock cycles consumed. + /// + /// + public int Execute() + { + // + // Run the DMA controller, see if it has anything to do this cycle. + // + _dma.Execute(); + + if (_dma.HRQ) + { + // Yes, it executed a DMA transfer which means the CPU doesn't get to run. + return 4; // A DMA cycle takes 4 clocks. + } + else + { + // Run the CPU for one instruction. + return _cpu.Execute(); + } + } + + public i8085 CPU + { + get { return _cpu; } + } + + public I8085MemoryBus Memory + { + get { return _mem; } + } + + public MiscIO MiscIO + { + get { return _miscIO; } + } + + public FloppyController FloppyController + { + get { return _floppyController; } + } + + public DMAController DMAController + { + get { return _dma; } + } + + public Keyboard Keyboard + { + get { return _keyboard; } + } + + public Mouse Mouse + { + get { return _mouse; } + } + + public Printer Printer + { + get { return _tty; } + } + + private i8085 _cpu; + private IOPIOBus _io; + private I8085MemoryBus _mem; + + // + // Devices on the IOP + // + private MiscIO _miscIO; + private FloppyController _floppyController; + private DMAController _dma; + private Keyboard _keyboard; + private Mouse _mouse; + private Printer _tty; + private DSystem _system; + + // + // Devices used by the IOP + // + private FloppyDrive _floppyDrive; + } +} diff --git a/D/IOP/Keyboard.cs b/D/IOP/Keyboard.cs new file mode 100644 index 0000000..6c359f6 --- /dev/null +++ b/D/IOP/Keyboard.cs @@ -0,0 +1,237 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System.Collections.Generic; +using System.Threading; + +namespace D.IOP +{ + /// + /// Defines the scancodes understood by the IOP. + /// The below is currently derived from work undertaken by Al Kossow + /// (many thanks.) + /// This table corresponds to the standard US keyboard layout for the + /// Star/1108. TODO: How to deal with international arrangements? + /// + public enum KeyCode + { + Invalid = 0x00, + + D1 = 0x10, + T10 = 0x11, + Defaults = 0x12, // T9 + LargerSmaller = 0x13, // T8 + Subscript = 0x14, // T7 + Undo = 0x15, // R6 + Superscript = 0x16, // T6 + Properties = 0x17, // L12 (Also Ctrl) + Move = 0x18, // L9 + Copy = 0x19, // L6 + Underline = 0x1a, // T5 + Italics = 0x1b, // T4 + Bold = 0x1c, // T3 + Center = 0x1d, // T2 + T1 = 0x1e, + + R4 = 0x20, + SkipNext = 0x22, // R1 + Help = 0x23, // R2 + Margins = 0x24, // R5 + R3 = 0x25, + L10 = 0x27, + Same = 0x28, // L7 + L4 = 0x29, + L1 = 0x2a, + A9 = 0x2d, + + DefnExpand = 0x30, // R7 (Also Esc) + R10 = 0x31, + Keyboard = 0x32, // R11 (^X) + Font = 0x33, // R8 (\,|) + R9 = 0x34, + Stop = 0x35, // R12 + Space = 0x36, + Open = 0x37, // L11 (Also Meta) + L8 = 0x38, + Find = 0x39, // L5 + Again = 0x3a, // L2 + Delete = 0x3b, // L3 + A8 = 0x3c, + A11 = 0x3d, + + A12 = 0x41, // Shift ? + RightShift = 0x42, // A6 + FSlash = 0x43, + Period = 0x44, + Comma = 0x45, + M = 0x46, + N = 0x47, + B = 0x48, + V = 0x49, + C = 0x4a, + X = 0x4b, + Z = 0x4c, + K47 = 0x4e, + + Return = 0x50, // A4 + BackQuote = 0x51, // K46 + Quote = 0x52, // K43 + Colon = 0x53, + L = 0x54, + K = 0x55, + J = 0x56, + H = 0x57, + G = 0x58, + F = 0x59, + D = 0x5a, + S = 0x5b, + A = 0x5c, + Lock = 0x5e, // A3 + LeftShift = 0x5f, // A5 + + A10 = 0x60, + RBracket = 0x61, // K45 + LBracket = 0x62, // K42 + P = 0x63, + O = 0x64, + I = 0x65, + U = 0x66, + Y = 0x67, + T = 0x68, + R = 0x69, + E = 0x6a, + W = 0x6b, + Q = 0x6c, + Tab = 0x6d, // A1 + D2 = 0x6f, + + Backspace = 0x70, // A2 + Equals = 0x71, + Minus = 0x72, + N0 = 0x73, + N9 = 0x74, + N8 = 0x75, + N7 = 0x76, + N6 = 0x77, + N5 = 0x78, + N4 = 0x79, + N3 = 0x7a, + N2 = 0x7b, + N1 = 0x7c, + FArrow = 0x7d, // K48 + + } + + public class Keyboard + { + public Keyboard() + { + _keyboardQueue = new Queue(); + + _lock = new ReaderWriterLockSlim(); + } + + public byte ReadData() + { + if (Log.Enabled) Log.Write(LogComponent.IOPKeyboard, "Key data {0} (0x{1:x2}) read.", _keyData, (int)_keyData); + return (byte)_keyData; + } + + public void NextData() + { + _lock.EnterUpgradeableReadLock(); + if (_keyboardQueue.Count > 0) + { + _lock.EnterWriteLock(); + _keyData = _keyboardQueue.Dequeue(); + if (Log.Enabled) Log.Write(LogComponent.IOPKeyboard, "Key data {0} (0x{1:x2}) dequeued.", _keyData, (int)_keyData); + _lock.ExitWriteLock(); + } + else + { + // No data available. + _keyData = KeyCode.Invalid; + } + _lock.ExitUpgradeableReadLock(); + } + + public bool DataReady() + { + _lock.EnterReadLock(); + bool ready = _keyboardQueue.Count > 0; + _lock.ExitReadLock(); + return ready; + } + + public void EnableDiagnosticMode() + { + // + // Per MoonIOPCSTest.asm: + // "Return sequence of events should be all characters held down followed by D2, D1." + // Right now, just enqueue d2, d1 + + _lock.EnterWriteLock(); + + _keyboardQueue.Enqueue(KeyCode.D2); + _keyboardQueue.Enqueue(KeyCode.D1); + + _lock.ExitWriteLock(); + } + + public void DisableDiagnosticMode() + { + _lock.EnterWriteLock(); + _keyboardQueue.Clear(); + _lock.ExitWriteLock(); + } + + public void KeyDown(KeyCode keycode) + { + _lock.EnterWriteLock(); + // Bit 0 = 0 indicates the key being pressed + _keyboardQueue.Enqueue((KeyCode)((int)keycode & 0x7f)); + _lock.ExitWriteLock(); + } + + public void KeyUp(KeyCode keycode) + { + _lock.EnterWriteLock(); + // Bit 0 = 1 indicates the key being released + _keyboardQueue.Enqueue((KeyCode)((int)keycode | 0x80)); + _lock.ExitWriteLock(); + } + + private KeyCode _keyData; + private Queue _keyboardQueue; + + private ReaderWriterLockSlim _lock; + + } +} diff --git a/D/IOP/MiscIO.cs b/D/IOP/MiscIO.cs new file mode 100644 index 0000000..0a54104 --- /dev/null +++ b/D/IOP/MiscIO.cs @@ -0,0 +1,453 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; + +namespace D.IOP +{ + public enum AltBootValues + { + None = -1, + DiagnosticRigid = 0, + Rigid, + Floppy, + Ethernet, + DiagnosticEthernet, + DiagnosticFloppy, + AlternateEthernet, + DiagnosticTrident1, + DiagnosticTrident2, + DiagnosticTrident3, + HeadCleaning + } + + public class MiscIO : IIOPDevice + { + public MiscIO(IOProcessor iop) + { + // + // Keep a reference to the IOP we belong to + // so we can poll the interrupt status of various + // devices. + // + _iop = iop; + + _todClock = new TODClock(); + + // Default to no alt boot + _altBoot = AltBootValues.None; + + Reset(); + } + + public int[] ReadPorts + { + get { return _readPorts; } + } + + public int[] WritePorts + { + get { return _writePorts; } + } + + /// + /// Allows UI to be alerted when the MP value changes. + /// + public delegate void MPChangedEventHandler(); + + public MPChangedEventHandler MPChanged; + + + public AltBootValues AltBoot + { + get + { + return _altBoot; + } + + set + { + _altBoot = value; + _altBootCounter = (int)value; + } + } + + public void Reset() + { + _mPanelValue = 0; + _mPanelBlank = true; + + _lastClockFlags = 0; + _dmaTestValue = 0; + + _altBootCounter = (int)_altBoot; + + _todClock.Reset(); + } + + public void WritePort(int port, byte value) + { + switch (port) + { + case 0xd0: + // + // DMA Test Register + // At this point, it appears all this does is store the value written, + // and return it when read. + // + _dmaTestValue = value; + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO port DMATest write {0:x2}", value); + break; + + case 0xe9: // KB, MP, TOD clocks + DoMiscClock(value); + break; + + case 0xea: // Clear TOD interrupt + _todClock.ClearInterrupt(); + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO TOD interrupt clear."); + break; + + case 0xed: // Clear Mouse X,Y counters + _iop.Mouse.Clear(); + break; + + case 0xef: // KB, MP, TOD control + // + // Control bits: + // 0x40 - pReadKBData - read KB data + // 0x20 - KBTone - KB speaker bit + // 0x10 - KBDiag - Set KB Diag mode + // 0x08 - BlankMPanel - Blank MPanel bit + // 0x04 - ReadTimeMode - Read TOD mode bit + // 0x02 - ClearTimeMode - Clear TOD mode bit + // 0x01 - SetTimeMode - Set TOD mode bit + // + _mPanelBlank = (value & 0x08) != 0; + MPChanged(); + + if ((value & 0x40) != 0) + { + // Prime the next byte of keyboard data. + _iop.Keyboard.NextData(); + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO Keyboard data clock."); + } + + if ((value & 0x10) != 0) + { + _iop.Keyboard.EnableDiagnosticMode(); + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO Keyboard diagnostic mode entered."); + } + + if ((value & 0x04) != 0) + { + _todClock.SetMode(TODAccessMode.Read); + } + + if ((value & 0x02) != 0) + { + _todClock.SetMode(TODAccessMode.Clear); + } + + if ((value & 0x01) != 0) + { + _todClock.SetMode(TODAccessMode.Set); + } + + break; + + default: + throw new InvalidOperationException(String.Format("Unexpected write to port {0:x2}", port)); + } + } + + public byte ReadPort(int port) + { + byte value; + switch(port) + { + case 0xd0: + // + // DMA Test Register: + // Just return whatever value was written. + // + value = _dmaTestValue; + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO port DMATest read {0:x2}", value); + break; + + case 0xef: + // + // MiscInput1: AltBoot,TimeData,PowerFailed,TODInt,CSParError,MouseSw1,Sw2,Sw3 + // + // Provide the AltBoot switch so we can allow floppy booting: + // The way the boot prom selects a boot device is to: + // 1) check for the AltBoot bit (meaning the alternate boot switch is held down) + // 2) if so, increment MP, wait 1 second. + // 3) repeat 1 + 2 until AltBoot is no longer set. + // 4) the final value is the device to be booted from. + // + // We decrement our boot device counter on every read until we hit zero at which point + // we "release" the button. + // + if (_altBootCounter > 0) + { + value = (int)MiscInput1Flags.AltBoot; + _altBootCounter--; + } + else + { + value = 0; + } + + // + // OR in other bits + // + value = (byte)(value | + _todClock.ReadClockBit() | + (_todClock.PowerLoss ? 0x20 : 0x0) | + (_todClock.Interrupt ? 0x10 : 0x0) | + (int)(_iop.Mouse.Buttons) | + (int)MiscInput1Flags.CSParity /* active low, we don't want parity errors */); + + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO port MiscInput1 read {0:x2}", value); + break; + + case 0xe9: + // + // Interrupt status register. + // Most of these aren't real interrupts (they don't trigger an 8085 interrupt) but are simply + // status flags raised by various bits of hardware that get polled by the main IOP code loop. + // This register is not a latch, it merely buffers these signals which are generated by their + // respective devices. + // We combine those bits here from their various sources. All of these signals are active low. + // + // Add more as more things get implemented... + // + value = (byte)~( + (_iop.FloppyController.Interrupt ? 0x80 : 0x00) | + (_iop.Keyboard.DataReady() ? 0x40 : 0x00)); + /* TODO: disabled until it can be completed + (_iop.Printer.TxRequest ? 0x20 : 0x00) | + (_iop.Printer.RxRequest ? 0x10 : 0x00)); */ + + // if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO port Interrupt Status read {0:x2}", value); + break; + + case 0xea: + // + // Keyboard data latch. Data is inverted, and bit 0 (msb) indicates keystroke up or down (1 = down). + // + value = (byte)(~_iop.Keyboard.ReadData()); + if (Log.Enabled) Log.Write(LogComponent.IOPMisc, "Misc IO port Keyboard Data read {0:x2}", value); + break; + + case 0xed: + // + // Mouse X counter + // + value = (byte)_iop.Mouse.MouseX; + break; + + case 0xee: + // + // Mouse Y counter + // + value = (byte)_iop.Mouse.MouseY; + break; + + default: + value = 0; + break; + } + + return value; + } + + /// + /// The value of the MP display (displayed in red LEDs on the front of the Star) + /// + public int MPanelValue + { + get { return _mPanelValue; } + } + + /// + /// Whether the MP display is currently turned on + /// + public bool MPanelBlank + { + get { return _mPanelBlank; } + } + + public TODClock TODClock + { + get { return _todClock; } + } + + private void DoMiscClock(byte clockFlags) + { + // + // From code in BootSubs.asm (not coincidentally labeled DoMiscClock): + // The various clocks are clocked manually by the 8085 by writing a zero + // to the appropriate clock bit followed by a 1 to the clock bit, on a 0->1 transition + // data is clocked into the appropriate register, or cleared/incremented for the MPanel display. + // + + // + // on a 1->0 transition for a clock bit we will take the appropriate action. + // + for (int clockFlag = 0x1; clockFlag < 0x100; clockFlag = clockFlag << 1) + { + if ((clockFlags & clockFlag) == 0 && + (_lastClockFlags & clockFlag) != 0) + { + switch((ClockFlags)clockFlag) + { + case ClockFlags.ClrMPanel: + _mPanelValue = 0; + MPChanged(); + break; + + case ClockFlags.IncMPanel: + _mPanelValue = (_mPanelValue + 1) % 10000; + MPChanged(); + break; + + case ClockFlags.TODRead: + _todClock.ClockBit(TODClockType.Read); + break; + + case ClockFlags.TODSetA: + _todClock.ClockBit(TODClockType.SetA); + break; + + case ClockFlags.TODSetB: + _todClock.ClockBit(TODClockType.SetB); + break; + + case ClockFlags.TODSetC: + _todClock.ClockBit(TODClockType.SetC); + break; + + case ClockFlags.TODSetD: + _todClock.ClockBit(TODClockType.SetD); + break; + } + } + } + + _lastClockFlags = clockFlags; + } + + // MP data + private bool _mPanelBlank; + private int _mPanelValue; + + // Alt boot counter -- decremented on access to MiscInput1, used to simulate + // holding down of AltBoot button for N seconds to select boot device. + private int _altBootCounter; + + // The value to set the AltBoot counter to at reset. + private AltBootValues _altBoot; + + // Clock register data + private int _lastClockFlags; + + // DMA Test Register data + private byte _dmaTestValue; + + // TOD Clock + private TODClock _todClock; + + // Reference to the IOP we belong to. + private IOProcessor _iop; + + private readonly int[] _readPorts = new int[] + { + 0xd0, // DMA Test Register + 0xe9, // Interrupt request bits (read) + 0xea, // Keyboard data latch + 0xed, // Mouse X counter + 0xee, // Mouse Y counter + 0xef // Miscellaneous input + }; + + private readonly int[] _writePorts = new int[] + { + 0xd0, // DMA Test Register + 0xe9, // KB, MP, TOD clocks (write) + 0xea, // Clear TOD interrupt (write) + 0xed, // Clear Mouse X,Y counters + 0xef // KB, MP, TOD control (write) + }; + + // + // Misc clocks flags + // + [Flags] + private enum ClockFlags + { + ClrMPanel = 0x40, + IncMPanel = 0x20, + TODRead = 0x10, + TODSetA = 0x08, + TODSetB = 0x04, + TODSetC = 0x02, + TODSetD = 0x01, + } + + [Flags] + private enum MiscInput1Flags + { + AltBoot = 0x80, + TODData = 0x40, + PowerFailed = 0x20, + TODInt = 0x10, + CSParity = 0x08, + MouseSw3 = 0x4, + MouseSw2 = 0x2, + MouseSw1 = 0x1, + } + + [Flags] + private enum InterruptRequestFlags + { + Floppy = 0x80, + Keyboard = 0x40, + PrinterTx = 0x20, + PrinterRx = 0x10, + Misc = 0x08, + RS232 = 0x04, + LSEPTx = 0x02, + LSEPRx = 0x01, + } + + } +} diff --git a/D/IOP/Mouse.cs b/D/IOP/Mouse.cs new file mode 100644 index 0000000..1a9ecfd --- /dev/null +++ b/D/IOP/Mouse.cs @@ -0,0 +1,103 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace D.IOP +{ + [Flags] + public enum StarMouseButton + { + None = 0, + Left = 0x4, + Right = 0x2, + Middle = 0x1, + } + + public class Mouse + { + public Mouse() + { + + } + + public int MouseX + { + get { return _mouseX; } + } + + public int MouseY + { + get { return _mouseY; } + } + + public StarMouseButton Buttons + { + get { return _buttons; } + } + + public void Clear() + { + _mouseX = 0; + _mouseY = 0; + } + + public void MouseDown(StarMouseButton button) + { + _buttons |= button; + } + + public void MouseUp(StarMouseButton button) + { + _buttons &= (~button); + } + + public void MouseMove(int dx, int dy) + { + _mouseX += dx; + _mouseY += dy; + + // Clip into range (-128 to 127) + _mouseX = Math.Max(-128, _mouseX); + _mouseX = Math.Min(127, _mouseX); + + _mouseY = Math.Max(-128, _mouseY); + _mouseY = Math.Min(127, _mouseY); + } + + private int _mouseX; + private int _mouseY; + + private StarMouseButton _buttons; + } +} diff --git a/D/IOP/PROM/537P03029.bin b/D/IOP/PROM/537P03029.bin new file mode 100644 index 0000000..a18da5b Binary files /dev/null and b/D/IOP/PROM/537P03029.bin differ diff --git a/D/IOP/PROM/537P03030.bin b/D/IOP/PROM/537P03030.bin new file mode 100644 index 0000000..f4f8875 Binary files /dev/null and b/D/IOP/PROM/537P03030.bin differ diff --git a/D/IOP/PROM/537P03032.bin b/D/IOP/PROM/537P03032.bin new file mode 100644 index 0000000..92a23c0 Binary files /dev/null and b/D/IOP/PROM/537P03032.bin differ diff --git a/D/IOP/PROM/537P03700.bin b/D/IOP/PROM/537P03700.bin new file mode 100644 index 0000000..83fa420 Binary files /dev/null and b/D/IOP/PROM/537P03700.bin differ diff --git a/D/IOP/Printer.cs b/D/IOP/Printer.cs new file mode 100644 index 0000000..2661a07 --- /dev/null +++ b/D/IOP/Printer.cs @@ -0,0 +1,123 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using D.Logging; + +namespace D.IOP +{ + /// + /// Stub implementation of the Printer port. This is just + /// enough to get the rigid diagnostic set to pass. + /// + public class Printer : IIOPDevice + { + public Printer() + { + Reset(); + } + + public void Reset() + { + _rxRequest = true; // active low + _txRequest = true; // always ready to transmit, makes the diags happy. + } + + public bool RxRequest + { + get { return _rxRequest; } + } + + public bool TxRequest + { + get { return _txRequest; } + } + + public int[] ReadPorts + { + get { return _readPorts; } + } + + public int[] WritePorts + { + get { return _writePorts; } + } + + public byte ReadPort(int port) + { + byte value = 0; + switch(port) + { + case 0x88: + if (Log.Enabled) Log.Write(LogComponent.IOPPrinter, "Stub: Printer data port read."); + value = _txData; // just loopback data to make diags happy. + break; + + case 0x89: + if (Log.Enabled) Log.Write(LogComponent.IOPPrinter, "Stub: Printer status port read. Returning DTR"); + value = 0x00; + _rxRequest = true; + break; + } + + return value; + } + + public void WritePort(int port, byte data) + { + switch(port) + { + case 0x88: + if (Log.Enabled) Log.Write(LogComponent.IOPPrinter, "Stub: Printer data port write 0x{0:x2}.", data); + _txData = data; + _rxRequest = false; // "TTY request is active low." + break; + + case 0x89: + if (Log.Enabled) Log.Write(LogComponent.IOPPrinter, "Stub: Printer control port write 0x{0:x2}.", data); + break; + } + } + + private bool _rxRequest; + private bool _txRequest; + + private byte _txData; + + private readonly int[] _readPorts = new int[] + { + 0x88, // data + 0x89, // status + }; + + private readonly int[] _writePorts = new int[] + { + 0x88, // data + 0x89, // commands + }; + } +} diff --git a/D/IOP/Source/SourceMap.txt b/D/IOP/Source/SourceMap.txt new file mode 100644 index 0000000..1756420 --- /dev/null +++ b/D/IOP/Source/SourceMap.txt @@ -0,0 +1,2532 @@ +# + # This file maps assembly source symbols to PROM/microcode addresses and source lines, forming a crude + # symbol table. + # + # Each source file is given a header a la: + # [FooSource.asm] + # (with brackets) + + # Each line's syntax is: + # , .. , :
, + # where '*none*' is a special symbol name meaning no symbol mapping is present. +[BootDefs.asm,v] +BootStackStart: 0x5fce,423 + +[BootMain.asm,v] +BootGoExt: 0x0040,171 +BootGo, SetInt: 0x0070,216 +MoveLinkTable: 0x02cc,700 +MoveLinkLoop: 0x02d7,705 +MoveItemLoop: 0x02d9,707 +*none*: 0x0071,218 +*none*: 0x0074,219 +*none*: 0x0077,220 +*none*: 0x02cf,701 +*none*: 0x02d2,702 +*none*: 0x02d4,703 +*none*: 0x02da,708 +*none*: 0x02db,709 +*none*: 0x02dc,710 +*none*: 0x02dd,711 +*none*: 0x02de,712 +*none*: 0x02e1,715 +*none*: 0x02e4,716 +*none*: 0x02e5,717 +*none*: 0x02e8,718 +*none*: 0x007a,224 +MoveLinkTableExt: 0x0067,197 +StartPhase0: 0x007d,232 +*none*: 0x0083,234 +*none*: 0x0080,233 +DoBootPhase: 0x0086,262 +StartBootBlock: 0x0089,264 +*none*: 0x008c,265 +*none*: 0x008f,266 +*none*: 0x0092,267 +*none*: 0x0095,268 +*none*: 0x0098,269 +*none*: 0x009a,270 +DoCSBlock: 0x009d,279 +*none*: 0x009e,280 +*none*: 0x009f,281 +*none*: 0x00a0,282 +*none*: 0x00a1,283 +*none*: 0x00a4,284 +*none*: 0x00a5,285 +*none*: 0x00a6,286 +*none*: 0x00a7,287 +*none*: 0x00aa,288 +*none*: 0x00ab,289 +*none*: 0x00ae,290 +*none*: 0x00b1,291 +*none*: 0x00b2,292 +*none*: 0x00b3,293 +*none*: 0x00b5,294 +NextCS: 0x00b8,296 +*none*: 0x00ba,297 +GetCSData: 0x00bd,299 +*none*: 0x00c0,300 +*none*: 0x00c1,301 +*none*: 0x00c4,303 +*none*: 0x00c7,305 +*none*: 0x00ca,306 +*none*: 0x00cb,307 +*none*: 0x00ce,308 +BootBlockDone: 0x00db,318 +*none*: 0x00d1,310 +*none*: 0x00d4,311 +*none*: 0x00d5,312 +*none*: 0x00d8,313 +*none*: 0x0213,538 +*none*: 0x0216,539 +*none*: 0x0223,552 +*none*: 0x0220,551 +*none*: 0x0224,553 +*none*: 0x021d,550 +*none*: 0x021c,542 +*none*: 0x0219,540 +*none*: 0x0227,554 +*none*: 0x022a,555 +Phase0CPStarted: 0x022d,560 +*none*: 0x0230,562 +InformCP: 0x02fe,743 +*none*: 0x0301,744 +*none*: 0x0304,745 +*none*: 0x0233,566 +*none*: 0x0236,567 +*none*: 0x0238,568 +*none*: 0x023b,569 +GoToPhase1Floppy: 0x0243,580 +*none*: 0x0246,581 +*none*: 0x0249,583 +*none*: 0x024b,584 +*none*: 0x024e,586 +*none*: 0x0251,587 +SetStartInitial: 0x0254,590 +*none*: 0x0257,591 +*none*: 0x025a,592 +*none*: 0x025c,593 +*none*: 0x025f,594 +*none*: 0x0261,595 +*none*: 0x0264,597 +*none*: 0x0267,598 +*none*: 0x0268,599 +*none*: 0x026b,600 +*none*: 0x026e,602 +DoSpecialBlock: 0x00de,327 +*none*: 0x00e1,328 +*none*: 0x00e3,329 +*none*: 0x0104,353 +*none*: 0x0107,354 +*none*: 0x010a,355 +*none*: 0x010d,356 +*none*: 0x010e,357 +*none*: 0x0111,358 +*none*: 0x0112,359 +*none*: 0x0115,360 +*none*: 0x0116,361 +*none*: 0x0119,362 +*none*: 0x00e6,330 +*none*: 0x00e8,331 +DoLastBlock: 0x01ea,517 +*none*: 0x00eb,332 +*none*: 0x00ed,333 +DoLoadIOP: 0x011c,371 +*none*: 0x011f,372 +*none*: 0x0122,373 +*none*: 0x0125,374 +*none*: 0x0128,375 +*none*: 0x012b,376 +*none*: 0x012e,377 +*none*: 0x0131,378 +*none*: 0x0132,379 +*none*: 0x0135,380 +*none*: 0x0138,381 +*none*: 0x013b,382 +DecrIOPCount: 0x014f,394 +*none*: 0x0152,395 +*none*: 0x0153,396 +*none*: 0x0156,397 +*none*: 0x0157,398 +*none*: 0x0158,399 +GetIOPData: 0x013e,386 +*none*: 0x0141,387 +*none*: 0x0144,388 +*none*: 0x0147,389 +*none*: 0x014a,390 +*none*: 0x014b,391 +*none*: 0x014c,392 +LastIOPData: 0x015b,405 +*none*: 0x015e,406 +*none*: 0x0160,407 +DoLastIOPByte: 0x016d,417 +*none*: 0x0170,418 +*none*: 0x0173,419 +*none*: 0x0176,420 +*none*: 0x0179,421 +*none*: 0x017a,422 +*none*: 0x0163,408 +*none*: 0x0165,409 +DoLastIOPWord: 0x017d,426 +*none*: 0x0180,427 +*none*: 0x0183,428 +*none*: 0x00f0,334 +*none*: 0x00f2,335 +*none*: 0x00f5,336 +*none*: 0x00f7,337 +*none*: 0x00fa,338 +*none*: 0x00fc,339 +UnknownBlock: 0x00ff,341 +*none*: 0x0101,342 +DoSetStartIOP: 0x01db,506 +*none*: 0x01de,507 +*none*: 0x01e7,510 +*none*: 0x01e1,508 +*none*: 0x01e4,509 +DoLoadU: 0x0186,444 +*none*: 0x0189,445 +*none*: 0x018a,446 +LoadUPhase0: 0x0192,454 +*none*: 0x0195,455 +*none*: 0x0196,456 +*none*: 0x0199,457 +*none*: 0x019a,458 +*none*: 0x019b,459 +*none*: 0x019c,460 +*none*: 0x019d,461 +*none*: 0x01a0,462 +*none*: 0x01a3,463 +*none*: 0x01a4,465 +*none*: 0x01a7,466 +*none*: 0x01a9,468 +*none*: 0x01aa,469 +*none*: 0x01ab,470 +*none*: 0x01ae,472 +*none*: 0x01b1,474 +*none*: 0x01ed,518 +*none*: 0x01f0,519 +*none*: 0x01f3,520 +*none*: 0x01f6,521 +*none*: 0x01f9,522 +*none*: 0x01fa,523 +*none*: 0x01fd,524 +FinishPhase0: 0x0200,529 +*none*: 0x0203,530 +*none*: 0x0206,532 +*none*: 0x0209,533 +*none*: 0x020a,534 +*none*: 0x020d,536 +*none*: 0x0210,537 +FinishPhaseNot0: 0x029f,653 +*none*: 0x02a2,654 +*none*: 0x02a5,655 +*none*: 0x02a8,657 +*none*: 0x02ab,658 +*none*: 0x02ac,659 +*none*: 0x02af,661 +*none*: 0x02b2,662 +*none*: 0x02b5,664 +*none*: 0x02b8,668 +*none*: 0x02bb,669 +*none*: 0x02bd,670 +*none*: 0x02c0,672 +*none*: 0x02c3,673 +*none*: 0x02c6,675 +*none*: 0x02c7,678 +StartIOP: 0x02c8,681 +*none*: 0x02cb,682 + +[BootSubs.asm,v] +BootInit: 0x0318,107 +SetIntMask: 0x0320,113 +*none*: 0x031a,108 +*none*: 0x031c,109 +*none*: 0x031e,110 +*none*: 0x0322,114 +*none*: 0x0323,119 +*none*: 0x0325,120 +*none*: 0x0326,121 +*none*: 0x0328,123 +*none*: 0x0329,124 +*none*: 0x032b,125 +*none*: 0x032d,126 +*none*: 0x032f,127 +*none*: 0x0331,128 +*none*: 0x0333,129 +*none*: 0x0335,131 +*none*: 0x0337,132 +*none*: 0x033a,134 +*none*: 0x033d,135 +*none*: 0x0340,136 +*none*: 0x0343,137 +*none*: 0x0346,138 +*none*: 0x0347,139 +*none*: 0x034a,140 +*none*: 0x034d,141 +*none*: 0x0350,142 +*none*: 0x0353,143 +*none*: 0x0356,144 +*none*: 0x0357,145 +*none*: 0x035a,146 +*none*: 0x035d,147 +*none*: 0x0360,148 +*none*: 0x0363,149 +*none*: 0x0366,151 +*none*: 0x0369,152 +*none*: 0x036c,156 +*none*: 0x036f,157 +*none*: 0x0372,159 +*none*: 0x0375,160 +*none*: 0x0378,161 +*none*: 0x037b,162 +*none*: 0x037e,163 +*none*: 0x0381,164 +*none*: 0x0384,165 +*none*: 0x0387,166 +DoMiscClock: 0x09aa,1687 +*none*: 0x09ac,1688 +*none*: 0x09ad,1689 +*none*: 0x09af,1690 +*none*: 0x09b0,1691 +*none*: 0x09b2,1692 +*none*: 0x09d2,1726 +*none*: 0x09cf,1725 +*none*: 0x09cd,1724 +PutMP: 0x09b3,1701 +*none*: 0x09e8,1752 +*none*: 0x09e9,1753 +*none*: 0x09e6,1751 +*none*: 0x09e3,1750 +*none*: 0x09e0,1748 +*none*: 0x09df,1747 +ErrorReport: 0x09de,1746 +*none*: 0x09fb,1773 +*none*: 0x09fc,1774 +*none*: 0x09fd,1777 +*none*: 0x09fe,1778 +*none*: 0x09ff,1779 +*none*: 0x0a00,1780 +*none*: 0x0a03,1781 +*none*: 0x0a04,1782 +*none*: 0x0a07,1784 +*none*: 0x0a08,1785 +*none*: 0x0a09,1786 +*none*: 0x09b6,1703 +*none*: 0x09b7,1704 +*none*: 0x09b9,1705 +*none*: 0x09bb,1706 +CheckMPCount: 0x09c3,1711 +*none*: 0x09c4,1712 +*none*: 0x09c5,1713 +*none*: 0x09c6,1714 +PutMPLoop: 0x09be,1708 +*none*: 0x09c0,1709 +*none*: 0x09c9,1716 +*none*: 0x09ca,1717 +*none*: 0x09cc,1718 +InitCSTPCImage: 0x03fe,311 +*none*: 0x0400,312 +*none*: 0x0402,313 +*none*: 0x0404,314 +*none*: 0x0407,316 +*none*: 0x0408,317 +*none*: 0x0409,318 +*none*: 0x040a,319 +*none*: 0x040b,320 +*none*: 0x040c,321 +*none*: 0x040f,325 +*none*: 0x0412,326 +*none*: 0x0413,327 +InitCSImageLoop: 0x0416,329 +*none*: 0x0418,330 +*none*: 0x0419,331 +*none*: 0x041a,332 +*none*: 0x041c,333 +*none*: 0x041d,334 +*none*: 0x041e,335 +*none*: 0x0420,336 +*none*: 0x0421,337 +*none*: 0x0422,338 +*none*: 0x0424,339 +*none*: 0x0425,340 +*none*: 0x0426,341 +*none*: 0x0428,342 +*none*: 0x0429,343 +*none*: 0x042a,344 +*none*: 0x042c,345 +*none*: 0x042d,346 +*none*: 0x042e,347 +*none*: 0x042f,348 +*none*: 0x0430,349 +*none*: 0x0431,350 +*none*: 0x0434,351 +StartNextRead: 0x0435,372 +*none*: 0x0438,373 +*none*: 0x043a,374 +*none*: 0x043d,375 +*none*: 0x043f,376 +*none*: 0x0440,377 +*none*: 0x0442,378 +GetNextWord: 0x0451,412 +*none*: 0x0454,413 +*none*: 0x0456,414 +GetNextIOP: 0x047c,446 +*none*: 0x047d,447 +*none*: 0x0480,448 +*none*: 0x0481,449 +*none*: 0x0482,450 +*none*: 0x0483,451 +*none*: 0x0484,452 +*none*: 0x0485,453 +*none*: 0x0486,454 +*none*: 0x0487,455 +*none*: 0x0488,456 +*none*: 0x048b,457 +*none*: 0x048c,458 +*none*: 0x0443,379 +WriteCS: 0x04b8,518 +*none*: 0x04bb,519 +*none*: 0x04be,520 +*none*: 0x04bf,521 +*none*: 0x04c0,522 +*none*: 0x04c1,523 +*none*: 0x04c2,524 +*none*: 0x04c3,525 +*none*: 0x04c4,527 +*none*: 0x04c7,530 +*none*: 0x04ca,531 +*none*: 0x04cc,532 +*none*: 0x04cf,533 +*none*: 0x04d2,534 +*none*: 0x04d3,535 +*none*: 0x04d5,536 +DoWriteTPC: 0x0695,933 +LeftAlignTPCAddr: 0x06ca,982 +*none*: 0x06cb,983 +*none*: 0x06cd,984 +*none*: 0x06ce,985 +*none*: 0x06cf,986 +*none*: 0x06d0,987 +*none*: 0x06d1,988 +*none*: 0x0698,934 +*none*: 0x0699,935 +*none*: 0x069a,936 +*none*: 0x069b,937 +*none*: 0x069c,938 +*none*: 0x069d,939 +*none*: 0x069f,940 +*none*: 0x06a0,942 +*none*: 0x06a1,943 +*none*: 0x06a2,944 +*none*: 0x06a3,945 +*none*: 0x06a4,946 +*none*: 0x06a7,947 +*none*: 0x06aa,949 +*none*: 0x06ad,950 +*none*: 0x06af,951 +BootWriteTPC: 0x06c4,969 +*none*: 0x06c5,970 +*none*: 0x06c6,971 +*none*: 0x06c7,972 +*none*: 0x06c8,973 +*none*: 0x06c9,974 +*none*: 0x04d8,538 +*none*: 0x04db,539 +DoWriteCS: 0x0656,870 +*none*: 0x0659,871 +*none*: 0x065c,873 +*none*: 0x065f,874 +*none*: 0x0661,875 +BootDoWriteCS: 0x0683,901 +BootDoWriteCSLoop: 0x0685,903 +*none*: 0x0688,904 +*none*: 0x0689,905 +*none*: 0x068a,906 +*none*: 0x068b,907 +*none*: 0x068c,908 +*none*: 0x068d,909 +*none*: 0x0690,910 +*none*: 0x0691,911 +*none*: 0x0694,912 +*none*: 0x04e6,543 +*none*: 0x04de,540 +*none*: 0x04e1,541 +*none*: 0x04e3,542 +WaitCPOutAck: 0x071d,1087 +*none*: 0x071f,1088 +*none*: 0x0721,1089 +*none*: 0x0724,1090 +*none*: 0x071b,1085 +*none*: 0x06dc,1009 +*none*: 0x06da,1008 +StartCPKernel: 0x06d2,1004 +*none*: 0x06d4,1005 +*none*: 0x06d6,1006 +*none*: 0x06d8,1007 +*none*: 0x06df,1010 +*none*: 0x09d3,1727 +*none*: 0x09d5,1728 +IncrMP: 0x09d6,1733 +*none*: 0x09d7,1734 +*none*: 0x09d9,1735 +*none*: 0x09dc,1736 +*none*: 0x09dd,1737 +StartCP: 0x06e0,1019 +*none*: 0x06e2,1020 +*none*: 0x06ee,1038 +InitCPCmd: 0x06eb,1037 +*none*: 0x06e5,1022 +*none*: 0x06ea,1024 +*none*: 0x06f1,1039 +*none*: 0x06f2,1040 +*none*: 0x06f3,1041 +*none*: 0x06f4,1042 +*none*: 0x06f5,1043 +WriteCPWord: 0x0725,1097 +*none*: 0x0726,1098 +*none*: 0x0729,1099 +*none*: 0x072a,1100 +*none*: 0x06f8,1044 +*none*: 0x06f9,1045 +*none*: 0x06fa,1046 +*none*: 0x06fc,1047 +*none*: 0x06ff,1049 +*none*: 0x0700,1050 +*none*: 0x0701,1051 +*none*: 0x0702,1052 +*none*: 0x0703,1053 +*none*: 0x0704,1054 +*none*: 0x0705,1055 +ReadCPByte: 0x0708,1065 +*none*: 0x070a,1066 +*none*: 0x070c,1067 +*none*: 0x070f,1068 +*none*: 0x0711,1069 +*none*: 0x0646,834 +*none*: 0x0647,835 +*none*: 0x064a,836 +*none*: 0x064b,837 +*none*: 0x064c,838 +*none*: 0x0643,833 +CheckAltBootDevice: 0x0392,206 +*none*: 0x0395,207 +*none*: 0x0396,208 +*none*: 0x0397,209 +*none*: 0x0398,210 +*none*: 0x039b,211 +*none*: 0x039c,212 +*none*: 0x039d,213 +*none*: 0x039e,214 +*none*: 0x03a8,238 +*none*: 0x03ab,239 +*none*: 0x03ad,240 +*none*: 0x03b0,241 +*none*: 0x03b1,242 +*none*: 0x03b2,243 +*none*: 0x03b4,244 +AltBootRigid: 0x03d0,265 +*none*: 0x03d1,266 +*none*: 0x03d3,267 +SetSA1000: 0x03ef,290 +*none*: 0x03f1,291 +SetGenericBootDevice: 0x03a1,218 +SetBootDevice: 0x03a4,220 +*none*: 0x03a7,221 +SetupUregisters: 0x0588,680 +*none*: 0x058b,681 +*none*: 0x058e,682 +*none*: 0x0591,683 +*none*: 0x0592,684 +CheckUCnt: 0x059f,692 +SetupULoop: 0x0595,687 +InterpretUBlock: 0x05a3,700 +*none*: 0x05a6,701 +*none*: 0x05a7,702 +*none*: 0x05a8,703 +*none*: 0x05a9,704 +*none*: 0x05aa,705 +*none*: 0x05ad,706 +*none*: 0x05ae,707 +*none*: 0x05b1,709 +*none*: 0x05b4,710 +*none*: 0x05b7,711 +*none*: 0x05ba,712 +*none*: 0x05bc,713 +*none*: 0x05bf,715 +*none*: 0x05c0,716 +*none*: 0x05c1,717 +*none*: 0x05c2,718 +*none*: 0x05c3,719 +*none*: 0x05c6,720 +*none*: 0x05c7,721 +*none*: 0x05ca,723 +*none*: 0x0598,688 +*none*: 0x059b,689 +*none*: 0x059c,690 +*none*: 0x05a2,693 +InformCPBootDevice: 0x05f5,761 +*none*: 0x05f7,762 +*none*: 0x05fa,764 +*none*: 0x05fd,765 +SendHostWord: 0x0616,795 +*none*: 0x0619,796 +ReadHAddrLoop: 0x061b,798 +*none*: 0x061c,799 +*none*: 0x061d,800 +*none*: 0x061f,801 +*none*: 0x0620,802 +*none*: 0x0621,803 +*none*: 0x0622,804 +*none*: 0x0624,805 +*none*: 0x0625,806 +*none*: 0x0626,807 +*none*: 0x0627,808 +*none*: 0x0628,809 +*none*: 0x0629,810 +*none*: 0x062b,812 +*none*: 0x062a,811 +*none*: 0x062c,813 +*none*: 0x062f,815 +*none*: 0x0632,816 +*none*: 0x0633,817 +*none*: 0x0636,818 +*none*: 0x0637,819 +*none*: 0x0600,766 +*none*: 0x0603,767 +*none*: 0x0606,769 +*none*: 0x0609,770 +*none*: 0x060c,772 +*none*: 0x060f,773 +ORVersionNo: 0x0612,775 +*none*: 0x0613,776 +FloppyInit: 0x08ea,1525 +*none*: 0x08ec,1526 +FloppyInitE: 0x08ef,1530 +*none*: 0x08f1,1531 +*none*: 0x08f3,1532 +*none*: 0x08f6,1534 +*none*: 0x08f8,1535 +InitializeAgain: 0x08fb,1537 +FDCReset: 0x0948,1601 +*none*: 0x094a,1602 +*none*: 0x094c,1603 +*none*: 0x094f,1604 +*none*: 0x0952,1605 +*none*: 0x0954,1606 +*none*: 0x0957,1607 +*none*: 0x0959,1608 +*none*: 0x095b,1609 +*none*: 0x095d,1610 +*none*: 0x0960,1611 +WaitFDCBusy: 0x0963,1614 +*none*: 0x0965,1615 +*none*: 0x0966,1616 +*none*: 0x0968,1617 +*none*: 0x096b,1618 +*none*: 0x096c,1619 +*none*: 0x08fe,1538 +*none*: 0x0900,1539 +InitializeStepLoop: 0x0902,1541 +*none*: 0x0903,1542 +SendCommand: 0x096d,1632 +*none*: 0x096f,1633 +*none*: 0x0972,1634 +*none*: 0x0975,1637 +*none*: 0x0977,1638 +*none*: 0x0979,1639 +*none*: 0x097b,1640 +*none*: 0x097e,1642 +*none*: 0x0906,1544 +*none*: 0x0980,1643 +*none*: 0x0981,1644 +*none*: 0x0983,1645 +*none*: 0x0986,1646 +*none*: 0x0908,1545 +*none*: 0x090b,1546 +*none*: 0x090c,1547 +*none*: 0x090f,1549 +*none*: 0x0912,1550 +*none*: 0x0913,1551 +DoType1Cmd: 0x089b,1447 +*none*: 0x089c,1448 +*none*: 0x089d,1449 +ClearFDCStateBit: 0x08e3,1514 +*none*: 0x08e6,1515 +*none*: 0x08e7,1516 +StoreNewBit: 0x08df,1505 +*none*: 0x08e0,1506 +*none*: 0x08e2,1507 +*none*: 0x08d8,1494 +*none*: 0x08db,1502 +*none*: 0x08de,1503 +*none*: 0x08d5,1493 +SetSD: 0x08d3,1492 +IssueType1Cmd: 0x08ac,1459 +*none*: 0x08ad,1460 +*none*: 0x08b0,1463 +*none*: 0x08b3,1464 +*none*: 0x08b5,1465 +*none*: 0x08b6,1466 +*none*: 0x08b7,1467 +CheckTrack: 0x08c0,1473 +*none*: 0x08c2,1474 +*none*: 0x08c3,1475 +UpdateCylinder: 0x08cb,1482 +*none*: 0x08cc,1483 +*none*: 0x08ce,1484 +ExitType1Cmd: 0x08d1,1486 +*none*: 0x08d2,1487 +*none*: 0x0915,1553 +*none*: 0x0918,1557 +*none*: 0x0919,1558 +CheckTrk0: 0x0928,1571 +*none*: 0x092b,1572 +*none*: 0x092d,1573 +RestoreGood: 0x0930,1579 +*none*: 0x0931,1580 +*none*: 0x0934,1581 +*none*: 0x0935,1582 +*none*: 0x0938,1583 +*none*: 0x093a,1584 +*none*: 0x093d,1585 +*none*: 0x093f,1586 +*none*: 0x0942,1587 +PhaseToMP: 0x0987,1660 +*none*: 0x098a,1661 +*none*: 0x098d,1662 +*none*: 0x0990,1663 +*none*: 0x0993,1664 +PhaseToMPLoop: 0x0994,1666 +*none*: 0x0997,1667 +*none*: 0x099a,1668 +*none*: 0x099d,1669 +*none*: 0x099e,1670 +*none*: 0x099f,1671 +*none*: 0x09a0,1672 +*none*: 0x09a1,1673 +*none*: 0x09a4,1675 +*none*: 0x09a5,1676 +*none*: 0x09a6,1677 +*none*: 0x09a9,1678 +*none*: 0x0459,415 +*none*: 0x045b,416 +*none*: 0x045e,417 +*none*: 0x0460,418 +UnimplBootSource: 0x0463,421 +*none*: 0x0466,422 +GetNextFloppy: 0x048d,464 +*none*: 0x048e,465 +*none*: 0x0491,466 +*none*: 0x0492,467 +*none*: 0x0493,468 +*none*: 0x0496,470 +*none*: 0x0497,471 +*none*: 0x049a,474 +*none*: 0x049b,475 +ReadSectorRun: 0x073e,1152 +*none*: 0x0741,1153 +*none*: 0x0744,1154 +*none*: 0x0747,1155 +*none*: 0x074a,1156 +*none*: 0x074d,1157 +CheckSideSwitch: 0x0750,1160 +*none*: 0x0753,1161 +*none*: 0x0756,1162 +*none*: 0x0757,1163 +CheckSeek: 0x075a,1166 +*none*: 0x075d,1167 +*none*: 0x0760,1168 +*none*: 0x0761,1169 +ReadSectorLoop: 0x0764,1173 +DoSeekCmd: 0x0813,1315 +*none*: 0x0816,1316 +*none*: 0x0817,1317 +*none*: 0x081a,1318 +*none*: 0x081b,1319 +*none*: 0x081d,1320 +SeekAgain: 0x0820,1322 +DoSeek: 0x088e,1433 +*none*: 0x088f,1434 +*none*: 0x0891,1435 +SeekCylinderOK: 0x0899,1443 +*none*: 0x08a0,1451 +*none*: 0x08a3,1452 +*none*: 0x08a4,1453 +*none*: 0x08a7,1455 +*none*: 0x08a9,1456 +*none*: 0x0823,1325 +*none*: 0x0824,1326 +DecrSeekRetry: 0x0825,1329 +*none*: 0x0767,1174 +*none*: 0x0768,1175 +*none*: 0x076b,1176 +*none*: 0x076c,1177 +*none*: 0x076e,1178 +ReadAgain: 0x0771,1180 +DoReadSector: 0x0831,1350 +*none*: 0x0832,1351 +*none*: 0x0834,1355 +*none*: 0x0837,1356 +*none*: 0x0838,1357 +*none*: 0x083a,1358 +*none*: 0x083b,1359 +*none*: 0x083d,1360 +*none*: 0x0840,1361 +*none*: 0x0841,1362 +*none*: 0x0842,1363 +*none*: 0x0844,1364 +*none*: 0x0845,1365 +*none*: 0x0847,1366 +*none*: 0x0849,1367 +*none*: 0x084b,1368 +DoReadContinue: 0x084d,1372 +*none*: 0x084e,1373 +*none*: 0x0851,1377 +*none*: 0x0853,1378 +*none*: 0x0854,1379 +*none*: 0x0858,1380 +*none*: 0x0860,1388 +*none*: 0x0863,1389 +*none*: 0x0865,1390 +*none*: 0x0868,1391 +*none*: 0x086b,1392 +*none*: 0x086d,1393 +DoReadDone: 0x0887,1414 +*none*: 0x0870,1395 +*none*: 0x0871,1396 +*none*: 0x0873,1397 +NoDmaEndCount1: 0x0876,1399 +*none*: 0x0878,1400 +GoodDmaEndCount1: 0x087b,1404 +*none*: 0x087d,1405 +*none*: 0x087f,1406 +*none*: 0x0882,1409 +*none*: 0x0884,1410 +*none*: 0x0888,1415 +*none*: 0x088a,1416 +*none*: 0x088d,1417 +*none*: 0x0774,1182 +*none*: 0x0775,1183 +*none*: 0x0784,1198 +*none*: 0x0787,1199 +ByteToWord: 0x072d,1109 +*none*: 0x072e,1110 +*none*: 0x072f,1111 +*none*: 0x0730,1112 +*none*: 0x0731,1113 +*none*: 0x0732,1114 +*none*: 0x0733,1115 +*none*: 0x0734,1116 +*none*: 0x078a,1200 +*none*: 0x078b,1201 +*none*: 0x078e,1202 +*none*: 0x078f,1203 +*none*: 0x0792,1204 +*none*: 0x0795,1205 +*none*: 0x0796,1206 +MoreSectors: 0x0799,1210 +*none*: 0x079c,1211 +*none*: 0x079d,1212 +*none*: 0x07a0,1213 +*none*: 0x07a1,1214 +*none*: 0x07a4,1215 +*none*: 0x07a7,1216 +*none*: 0x07a8,1217 +*none*: 0x07ab,1218 +*none*: 0x07ac,1219 +*none*: 0x07af,1220 +SectorRunDone: 0x07b2,1226 +*none*: 0x07b5,1227 +*none*: 0x07b8,1228 +*none*: 0x07bb,1229 +*none*: 0x07bc,1230 +*none*: 0x07bf,1231 +*none*: 0x07c0,1232 +*none*: 0x07c3,1235 +*none*: 0x07c6,1236 +*none*: 0x07c9,1237 +*none*: 0x049e,476 +GetNextBuffer: 0x049f,479 +D1: 0x04a2,481 +*none*: 0x04a3,483 +*none*: 0x04a4,484 +*none*: 0x04a5,485 +D2: 0x04a6,487 +*none*: 0x04a7,489 +*none*: 0x04a8,490 +*none*: 0x04a9,491 +*none*: 0x04aa,492 +D3: 0x04ab,494 +*none*: 0x04ac,496 +*none*: 0x04af,497 +*none*: 0x04b2,498 +*none*: 0x04b3,499 +*none*: 0x04b6,500 +*none*: 0x04b7,501 +*none*: 0x06b2,952 +*none*: 0x06b4,954 +*none*: 0x06b6,955 +*none*: 0x06b7,956 +*none*: 0x06b8,957 +*none*: 0x06b9,958 +*none*: 0x06ba,959 +*none*: 0x06bb,960 +*none*: 0x06bd,961 +*none*: 0x06bf,963 +*none*: 0x06c1,964 +*none*: 0x0666,879 +*none*: 0x0664,877 +*none*: 0x0669,880 +*none*: 0x066b,882 +*none*: 0x066d,883 +*none*: 0x066e,884 +*none*: 0x066f,885 +*none*: 0x0670,886 +*none*: 0x0672,887 +*none*: 0x0674,889 +*none*: 0x0676,890 +NextWriteCS: 0x0679,892 +*none*: 0x067a,893 +*none*: 0x067b,894 +*none*: 0x067e,895 +*none*: 0x067f,896 +*none*: 0x06e7,1023 +*none*: 0x0682,897 +DoCSImage: 0x04e7,548 +*none*: 0x04ea,549 +*none*: 0x04eb,550 +*none*: 0x04ec,551 +*none*: 0x04ed,552 +*none*: 0x04ee,553 +*none*: 0x04ef,554 +*none*: 0x04f0,555 +*none*: 0x04f3,556 +*none*: 0x04f4,561 +*none*: 0x04f7,562 +*none*: 0x04f9,564 +*none*: 0x04fa,565 +*none*: 0x04fb,566 +*none*: 0x04fc,567 +*none*: 0x04fd,568 +*none*: 0x04fe,569 +*none*: 0x0501,570 +ReadMainMem: 0x0638,828 +*none*: 0x063b,829 +*none*: 0x063d,830 +*none*: 0x0640,831 +*none*: 0x09f6,1760 +*none*: 0x09f8,1761 +ErrorTrap: 0x09f1,1758 +*none*: 0x09f3,1759 + +[StartIOPBootProm.asm,v] +StartPreBoot: 0x0a0a,69 + +[PreBootSource.asm,v] +PreBootGo: 0x183b,108 +CommonHalt: 0x1974,316 +*none*: 0x183e,110 +GeneralRegTest: 0x1889,168 +*none*: 0x188a,169 +GeneralRegLoop: 0x188c,172 +*none*: 0x188e,173 +*none*: 0x188f,174 +*none*: 0x1891,175 +*none*: 0x1892,176 +*none*: 0x1895,177 +*none*: 0x1896,178 +*none*: 0x1897,179 +*none*: 0x189a,180 +*none*: 0x189c,181 +ThreeRegAddrTest: 0x189e,183 +*none*: 0x18a0,184 +*none*: 0x18a2,185 +*none*: 0x18a4,186 +*none*: 0x18a6,187 +*none*: 0x18a8,188 +*none*: 0x18aa,189 +*none*: 0x18ab,190 +*none*: 0x18ad,191 +*none*: 0x18af,192 +*none*: 0x18b2,193 +*none*: 0x18b4,194 +*none*: 0x18b6,195 +*none*: 0x18b9,196 +*none*: 0x18bb,197 +*none*: 0x18bd,198 +*none*: 0x18c0,199 +*none*: 0x1841,111 +*none*: 0x1842,114 +RamTest: 0x18c1,219 +NextStep: 0x18c3,221 +*none*: 0x18c4,222 +Step1: 0x18c5,224 +*none*: 0x18c7,225 +*none*: 0x18ca,226 +*none*: 0x18cd,227 +*none*: 0x195f,301 +*none*: 0x1961,302 +WriteStep: 0x1966,306 +WriteLoop: 0x1969,308 +*none*: 0x196a,309 +*none*: 0x196b,310 +*none*: 0x196c,311 +*none*: 0x196e,312 +*none*: 0x1971,313 +Step2: 0x18d0,229 +*none*: 0x18d2,230 +*none*: 0x18d5,231 +*none*: 0x18d8,232 +Step3: 0x18db,234 +*none*: 0x18dd,235 +*none*: 0x18e0,236 +*none*: 0x18e3,237 +WriteStepF: 0x1964,304 +Step4: 0x18e6,239 +*none*: 0x18e8,240 +*none*: 0x18eb,241 +*none*: 0x18ee,242 +*none*: 0x194b,287 +ReadStep: 0x194d,289 +ReadLoop: 0x1950,291 +*none*: 0x1951,292 +*none*: 0x1952,293 +*none*: 0x1955,294 +*none*: 0x1956,295 +*none*: 0x1957,296 +*none*: 0x1959,297 +*none*: 0x195c,298 +Step5: 0x18f1,244 +*none*: 0x18f3,245 +*none*: 0x18f6,246 +*none*: 0x18f9,247 +*none*: 0x18fc,249 +*none*: 0x18fe,250 +*none*: 0x1901,251 +*none*: 0x1904,252 +Step7: 0x1907,254 +*none*: 0x1909,255 +*none*: 0x190c,256 +*none*: 0x190f,257 +ReadStepF: 0x1946,284 +*none*: 0x1948,285 +Step8: 0x1912,259 +*none*: 0x1914,260 +*none*: 0x1917,261 +*none*: 0x191a,262 +Step9: 0x191d,264 +*none*: 0x191f,265 +*none*: 0x1922,266 +*none*: 0x1925,267 +Step10: 0x1928,269 +*none*: 0x192a,270 +*none*: 0x192d,271 +*none*: 0x1930,272 +Step11: 0x1933,274 +*none*: 0x1935,275 +*none*: 0x1938,276 +*none*: 0x193b,277 +Step12: 0x193e,279 +*none*: 0x1940,280 +*none*: 0x1943,281 +ReturnFromRamTest: 0x1845,117 +*none*: 0x1848,118 +*none*: 0x1849,119 +*none*: 0x1886,149 +*none*: 0x184c,120 +*none*: 0x184f,121 +*none*: 0x1852,122 +*none*: 0x1855,123 +*none*: 0x1858,124 +*none*: 0x185b,125 +*none*: 0x185e,126 +*none*: 0x1861,127 +*none*: 0x1864,129 +*none*: 0x1865,130 +*none*: 0x1868,132 +*none*: 0x186b,133 +*none*: 0x186e,134 +EndOfTests: 0x1871,137 +*none*: 0x1873,138 +*none*: 0x1876,139 +*none*: 0x1878,140 +*none*: 0x187b,141 +*none*: 0x187c,142 +Exit: 0x187f,144 +*none*: 0x1882,145 +*none*: 0x1885,146 + +[8085SelfTest.asm,v] +I8085SelfTest: 0x19c2,71 +EndJumpTest,BranchTest: 0x19c9,87 +BrTst1: 0x19d3,93 +BrTst2: 0x19dc,97 +BrTst3: 0x19e2,100 +BrTst4: 0x19e8,103 +BrTst5: 0x19f8,110 +RegisterTest: 0x1a01,133 +RegPat1: 0x1a93,231 +RegPat2: 0x1a95,234 +*none*: 0x19c3,72 +*none*: 0x19c6,73 +*none*: 0x19c7,74 +*none*: 0x19c8,75 +*none*: 0x19ca,89 +*none*: 0x19cd,90 +*none*: 0x19d0,91 +*none*: 0x19d6,94 +*none*: 0x19d9,95 +*none*: 0x19df,98 +*none*: 0x19e5,101 +*none*: 0x19eb,104 +*none*: 0x19ee,105 +*none*: 0x19ef,106 +*none*: 0x19f2,107 +*none*: 0x19f5,108 +*none*: 0x19fb,111 +*none*: 0x19fe,112 +*none*: 0x1a03,134 +*none*: 0x1a05,135 +*none*: 0x1a07,136 +*none*: 0x1a09,137 +*none*: 0x1a0b,138 +*none*: 0x1a0d,139 +*none*: 0x1a0f,140 +*none*: 0x1a11,141 +*none*: 0x1a14,142 +*none*: 0x1a9d,242 +*none*: 0x1aa0,243 +*none*: 0x1a9c,241 +*none*: 0x1aa1,244 +*none*: 0x1aa4,245 +*none*: 0x1aa5,246 +*none*: 0x1a96,235 +*none*: 0x1a97,236 +*none*: 0x1a98,237 +*none*: 0x1a99,238 +*none*: 0x1a9a,239 +*none*: 0x1a9b,240 +*none*: 0x1aa8,272 +*none*: 0x1aaa,273 +*none*: 0x1aac,274 +*none*: 0x1aad,275 +*none*: 0x1aae,277 +*none*: 0x1aaf,278 +*none*: 0x1ab0,279 +*none*: 0x1ab1,280 +*none*: 0x1ab2,281 +*none*: 0x1ab5,282 +*none*: 0x1ab8,283 +*none*: 0x1ab9,284 +*none*: 0x1aba,285 +*none*: 0x1abd,286 +*none*: 0x1abe,287 +*none*: 0x1abf,288 +*none*: 0x1ac2,289 +*none*: 0x1ac3,290 +*none*: 0x1ac4,291 +*none*: 0x1ac5,292 +*none*: 0x1ac6,293 +*none*: 0x1ac7,294 +*none*: 0x1ac8,295 +*none*: 0x1ac9,296 +*none*: 0x1aca,297 +*none*: 0x1acb,298 +*none*: 0x1acc,299 +*none*: 0x1acd,300 +*none*: 0x1ace,301 +*none*: 0x1acf,302 +*none*: 0x1ad0,303 +*none*: 0x1ad3,305 +*none*: 0x1a15,143 +*none*: 0x1a17,144 +*none*: 0x1a1a,145 +*none*: 0x1a1b,146 +*none*: 0x1a1d,147 +*none*: 0x1a20,148 +*none*: 0x1a21,149 +*none*: 0x1a23,150 +*none*: 0x1a26,151 +*none*: 0x1a27,152 +*none*: 0x1a29,153 +*none*: 0x1a2c,154 +*none*: 0x1a2d,155 +*none*: 0x1a2f,156 +*none*: 0x1a32,157 +*none*: 0x1a33,158 +*none*: 0x1a35,159 +*none*: 0x1a38,160 +*none*: 0x1a3a,161 +*none*: 0x1a3c,162 +*none*: 0x1a3e,163 +*none*: 0x1a40,164 +*none*: 0x1a42,165 +*none*: 0x1a44,166 +*none*: 0x1a46,167 +*none*: 0x1a48,168 +*none*: 0x1a4b,169 +*none*: 0x1a4d,170 +*none*: 0x1a4e,171 +*none*: 0x1a51,172 +*none*: 0x1a52,173 +*none*: 0x1a55,174 +*none*: 0x1a56,175 +*none*: 0x1a59,176 +*none*: 0x1a5b,177 +*none*: 0x1a5c,178 +*none*: 0x1a5d,179 +*none*: 0x1a60,180 +*none*: 0x1a62,181 +*none*: 0x1a63,182 +*none*: 0x1a64,183 +*none*: 0x1a67,184 +*none*: 0x1a69,185 +*none*: 0x1a6a,186 +*none*: 0x1a6b,187 +*none*: 0x1a6e,188 +*none*: 0x1a70,189 +*none*: 0x1a71,190 +*none*: 0x1a72,191 +*none*: 0x1a75,192 +*none*: 0x1a77,193 +*none*: 0x1a78,194 +*none*: 0x1a79,195 +*none*: 0x1a7c,196 +*none*: 0x1a7e,197 +*none*: 0x1a80,198 +*none*: 0x1a81,199 +*none*: 0x1a83,200 +*none*: 0x1a86,201 +*none*: 0x1a88,202 +*none*: 0x1a89,203 +*none*: 0x1a8b,204 +*none*: 0x1a8e,205 +*none*: 0x1a90,206 +*none*: 0x1a91,207 +*none*: 0x1ad4,306 +*none*: 0x1ad5,307 +*none*: 0x1ad6,308 +*none*: 0x1ad7,309 +*none*: 0x1ad8,310 +*none*: 0x1ad9,311 +*none*: 0x1ada,312 +*none*: 0x1adb,313 +*none*: 0x1adc,314 +*none*: 0x1add,315 +*none*: 0x1ade,316 +*none*: 0x1adf,317 +*none*: 0x1ae0,318 +*none*: 0x1ae1,319 +*none*: 0x1ae2,320 +*none*: 0x1ae3,321 +*none*: 0x1ae6,322 +*none*: 0x1ae7,323 +*none*: 0x1aea,324 +*none*: 0x1aeb,325 +*none*: 0x1aec,326 +*none*: 0x1aef,327 +*none*: 0x1af0,328 +SPregTest: 0x1af1,376 +*none*: 0x1af4,378 +*none*: 0x1af5,379 +*none*: 0x1af6,380 +*none*: 0x1af7,381 +*none*: 0x1af8,382 +*none*: 0x1af9,383 +*none*: 0x1afc,384 +*none*: 0x1aff,385 +*none*: 0x1b00,386 +*none*: 0x1b01,387 +*none*: 0x1b02,388 +*none*: 0x1b03,389 +*none*: 0x1b04,390 +*none*: 0x1b07,391 +*none*: 0x1b08,392 +*none*: 0x1b09,393 +*none*: 0x1b0c,394 +*none*: 0x1b0d,395 +*none*: 0x1b0e,396 +*none*: 0x1b11,397 +*none*: 0x1b12,398 +*none*: 0x1b15,399 +EndSPregTest: 0x1b16,403 +ParityTest: 0x1b19,414 +*none*: 0x1b1a,415 +*none*: 0x1b1c,416 +*none*: 0x1b1f,417 +*none*: 0x1b21,418 +*none*: 0x1b24,419 +ExitSelfTest: 0x1b27,485 + +[RSTLinksF.asm,v] +RSTTrap: 0x1830,102 +*none*: 0x1833,103 +*none*: 0x1836,104 +*none*: 0x1838,105 +RST7Link: 0x181e,87 + +[RST.asm,v] +RST7: 0x0038,127 + +[AltBoot.asm,v] +ReadAltBoot: 0x1c61,82 +*none*: 0x1c64,84 +*none*: 0x1c66,85 +ReadAltLoop: 0x1c69,90 +*none*: 0x1c6c,91 +*none*: 0x1c6f,92 +*none*: 0x1c72,93 +*none*: 0x1c75,94 +*none*: 0x1c77,95 +*none*: 0x1c79,96 +EndReadAlt: 0x1c9b,126 +*none*: 0x1c9e,127 +*none*: 0x1ca0,128 +*none*: 0x1ca3,129 +*none*: 0x1ca5,130 +*none*: 0x1ca8,132 +*none*: 0x1ca9,133 +*none*: 0x1caa,134 +*none*: 0x1cab,135 +*none*: 0x1cae,136 +*none*: 0x1caf,137 +*none*: 0x1cb0,138 +*none*: 0x1cb3,139 +*none*: 0x1cb4,140 + +[FDCTest.asm,v] +FDCTest: 0x1b28,92 +*none*: 0x1b2b,94 +*none*: 0x1b2d,95 +*none*: 0x1b30,96 +*none*: 0x1b32,97 +*none*: 0x1b35,98 +StartFDCTest: 0x1b36,100 +*none*: 0x1b38,101 +ResetFDC: 0x1b3a,105 +*none*: 0x1b3c,106 +*none*: 0x1b3e,107 +*none*: 0x1b40,108 +WaitV: 0x1c5c,295 +*none*: 0x1c5d,296 +Wait9: 0x1c60,298 +*none*: 0x1b43,109 +*none*: 0x1b45,110 +*none*: 0x1b47,111 +Wait: 0x1c5a,293 +*none*: 0x1b4a,112 +ReadFDCStatus: 0x1c41,271 +*none*: 0x1c44,272 +*none*: 0x1c46,273 +*none*: 0x1c49,274 +*none*: 0x1c4a,275 +*none*: 0x1c4c,276 +*none*: 0x1c4f,277 +*none*: 0x1b4d,117 +SeekTest: 0x1b50,119 +*none*: 0x1b52,120 +*none*: 0x1b54,121 +SetParam: 0x1b56,123 +*none*: 0x1b57,124 +*none*: 0x1b59,125 +*none*: 0x1b5a,126 +*none*: 0x1b5c,127 +*none*: 0x1b5d,128 +*none*: 0x1b5f,129 +GetFDCStatusB: 0x1c50,280 +*none*: 0x1c52,281 +*none*: 0x1c53,282 +*none*: 0x1c55,283 +*none*: 0x1c58,284 +*none*: 0x1c59,285 +*none*: 0x1b62,130 +*none*: 0x1b65,132 +SeekError: 0x1b67,134 +*none*: 0x1b6a,136 +*none*: 0x1b6c,137 +WrongTrack: 0x1b6d,139 +*none*: 0x1b70,141 +*none*: 0x1b73,142 +*none*: 0x1b75,143 +*none*: 0x1b77,144 +*none*: 0x1b7a,145 +ClearandReadStatus: 0x1b7d,148 +*none*: 0x1b7f,149 +FDCReset: 0x1c2b,257 +*none*: 0x1c2d,258 +*none*: 0x1c2f,259 +*none*: 0x1c31,260 +*none*: 0x1c34,261 +*none*: 0x1c35,262 +*none*: 0x1c37,263 +*none*: 0x1c39,264 +*none*: 0x1c3b,265 +*none*: 0x1c3d,266 +*none*: 0x1c40,267 +*none*: 0x1b82,150 +*none*: 0x1b85,153 +*none*: 0x1b88,154 +*none*: 0x1b8b,155 +*none*: 0x1b8c,156 +*none*: 0x1b8e,157 +*none*: 0x1b91,159 +*none*: 0x1b94,160 +*none*: 0x1b95,161 +*none*: 0x1b97,162 +*none*: 0x1b9a,164 +*none*: 0x1b9d,165 +*none*: 0x1b9e,166 +*none*: 0x1ba0,167 +*none*: 0x1ba3,169 +*none*: 0x1ba6,170 +*none*: 0x1ba7,172 +*none*: 0x1baa,173 +*none*: 0x1bab,174 +*none*: 0x1bad,175 +*none*: 0x1bb0,177 +*none*: 0x1bb3,178 +*none*: 0x1bb4,179 +*none*: 0x1bb6,180 +*none*: 0x1bb9,182 +*none*: 0x1bbc,183 +*none*: 0x1bbd,184 +*none*: 0x1bbf,185 +*none*: 0x1bc2,187 +*none*: 0x1bc5,188 +*none*: 0x1bc6,189 +*none*: 0x1bc8,190 +*none*: 0x1bcb,192 +*none*: 0x1bce,193 +*none*: 0x1bcf,194 +*none*: 0x1bd1,195 +*none*: 0x1bd4,197 +*none*: 0x1bd7,198 +*none*: 0x1bd8,199 +*none*: 0x1bda,200 +*none*: 0x1bdd,202 +*none*: 0x1be0,203 +*none*: 0x1be1,204 +*none*: 0x1be3,205 +*none*: 0x1be6,207 +*none*: 0x1be9,208 +*none*: 0x1bea,209 +*none*: 0x1bec,210 +*none*: 0x1bef,212 +TestIndexPulses: 0x1bf3,220 +*none*: 0x1bf4,221 +*none*: 0x1bf6,222 +*none*: 0x1bf8,223 +*none*: 0x1bf9,224 +*none*: 0x1bfc,227 +*none*: 0x1bff,228 +*none*: 0x1c02,229 +*none*: 0x1c05,231 +*none*: 0x1c08,232 +*none*: 0x1c22,247 +*none*: 0x1c23,248 +*none*: 0x1c24,249 +*none*: 0x1c25,250 +*none*: 0x1c0b,233 +*none*: 0x1c0d,234 +*none*: 0x1c10,237 +*none*: 0x1c13,238 +HighIndex: 0x1c16,240 +*none*: 0x1c19,241 +*none*: 0x1c1c,242 +*none*: 0x1c1d,243 +*none*: 0x1c20,244 + +[IOPInit.asm,v] +IOPInitialGo: 0x3000,94 +IOPInitialStart: 0x301d,146 +*none*: 0x3020,147 +*none*: 0x3021,148 +*none*: 0x3024,149 +*none*: 0x3027,150 +*none*: 0x3028,151 +*none*: 0x302b,153 +*none*: 0x302e,154 +*none*: 0x302f,155 +CheckInitDiagFloppy: 0x31c8,486 +*none*: 0x31cb,487 +*none*: 0x31cc,488 +*none*: 0x31cd,490 +InitialWaitMem1: 0x3032,158 +*none*: 0x3035,159 +*none*: 0x3038,164 +*none*: 0x3039,165 +*none*: 0x303a,166 +*none*: 0x303b,167 +FloppyInitialDone: 0x3046,177 +*none*: 0x303e,169 +*none*: 0x303f,170 +*none*: 0x3040,171 +*none*: 0x3043,173 +FloppyInitialError: 0x3055,185 +*none*: 0x3049,178 +*none*: 0x304c,179 +*none*: 0x304f,180 +*none*: 0x3052,181 +DoInterpretCPR: 0x3058,202 +*none*: 0x305b,203 +*none*: 0x305e,204 +*none*: 0x305f,205 +SetDiskAddr: 0x31ab,458 +*none*: 0x31ac,459 +*none*: 0x31ad,460 +*none*: 0x31ae,461 +*none*: 0x31af,462 +*none*: 0x31b0,463 +*none*: 0x31b4,465 +*none*: 0x31b3,464 +*none*: 0x31b5,466 +*none*: 0x31b8,467 +*none*: 0x31b9,468 +*none*: 0x31ba,469 +*none*: 0x31bb,470 +*none*: 0x31bc,471 +*none*: 0x31bd,472 +*none*: 0x31c0,473 +*none*: 0x31c1,475 +*none*: 0x31c2,476 +*none*: 0x31c5,477 +StartCPRBlock: 0x3067,212 +*none*: 0x306a,213 +*none*: 0x306d,214 +*none*: 0x306e,215 +*none*: 0x3071,216 +*none*: 0x3072,217 +*none*: 0x3075,219 +*none*: 0x3076,220 +EndCPRFile: 0x3079,223 +DoCPRBlock: 0x307c,231 +*none*: 0x307f,232 +GetCPRAddr: 0x3082,235 +*none*: 0x3085,236 +*none*: 0x3088,237 +*none*: 0x308b,238 +StartCPRWrite: 0x308e,241 +*none*: 0x3090,242 +*none*: 0x3093,243 +CPRBlockLoop: 0x3096,245 +*none*: 0x3099,246 +*none*: 0x309c,247 +*none*: 0x309f,248 +*none*: 0x30a0,249 +*none*: 0x30a3,251 +*none*: 0x30a6,252 +*none*: 0x30a7,253 +*none*: 0x30aa,254 +*none*: 0x30ab,255 +*none*: 0x30ac,256 +*none*: 0x30af,257 +*none*: 0x30b0,258 +*none*: 0x30b3,260 + +[BookKeepingTask.asm,v] +WaitTODIntr: 0x231a,526 +*none*: 0x231e,528 +*none*: 0x231c,527 +*none*: 0x2323,531 +*none*: 0x2321,529 +*none*: 0x2325,532 +*none*: 0x2328,533 +*none*: 0x232a,541 +*none*: 0x232c,542 +ReadTODByteLoop: 0x232e,544 +*none*: 0x2331,545 +*none*: 0x2332,546 +*none*: 0x2333,547 +*none*: 0x2334,548 +*none*: 0x2336,549 +*none*: 0x2338,550 +*none*: 0x2339,551 +*none*: 0x233a,552 +*none*: 0x233b,553 +*none*: 0x233c,555 +*none*: 0x233d,556 +*none*: 0x233f,557 +*none*: 0x2342,558 +*none*: 0x2343,559 +BitCounter: 0x2345,562 +*none*: 0x2346,563 +*none*: 0x2349,565 +*none*: 0x234a,566 +*none*: 0x234b,567 +*none*: 0x234c,568 +*none*: 0x234f,570 +*none*: 0x2351,571 +*none*: 0x2318,523 +*none*: 0x22f4,498 +*none*: 0x22f1,497 +*none*: 0x22f7,499 +*none*: 0x22fa,501 +*none*: 0x22fc,502 +*none*: 0x22fe,503 +*none*: 0x2301,505 +*none*: 0x2304,506 +SetTODValid: 0x2307,508 +*none*: 0x230a,509 +*none*: 0x230b,510 +*none*: 0x230e,511 +ValidTime: 0x230f,514 +ReadKeyboard: 0x2435,751 +*none*: 0x2437,752 +*none*: 0x243a,753 +*none*: 0x243d,754 +*none*: 0x2440,755 +*none*: 0x2442,756 +*none*: 0x2445,757 +*none*: 0x2247,758 +*none*: 0x2448,759 +*none*: 0x244b,766 +*none*: 0x244d,767 +*none*: 0x244e,768 +*none*: 0x2450,769 +*none*: 0x2453,770 +*none*: 0x2454,771 +*none*: 0x2455,772 +*none*: 0x2456,773 +*none*: 0x2459,774 +*none*: 0x245a,775 +*none*: 0x245b,776 +*none*: 0x245c,777 +*none*: 0x245d,778 +*none*: 0x245e,779 +*none*: 0x2461,780 +*none*: 0x2462,781 +*none*: 0x2463,782 +*none*: 0x2464,783 +*none*: 0x2465,784 +*none*: 0x2467,785 +*none*: 0x2468,786 +KeyEvent: 0x246a,789 +*none*: 0x246b,790 +*none*: 0x246c,791 +*none*: 0x246f,794 +*none*: 0x2470,795 +*none*: 0x2473,797 +*none*: 0x2474,799 +*none*: 0x2475,800 +*none*: 0x2478,801 +MainLoop: 0x24ab,844 +*none*: 0x24d1,869 +*none*: 0x24d3,872 +*none*: 0x24d4,873 +*none*: 0x24d7,875 +TODChangeFlag: 0x24d9,878 +ReadCSB: 0x24e6,886 +*none*: 0x24e9,887 +*none*: 0x24ec,891 +*none*: 0x24ef,892 +*none*: 0x24f0,893 +*none*: 0x24f3,894 +ReadMouse: 0x2290,412 +*none*: 0x2293,413 +*none*: 0x2294,414 +*none*: 0x2297,415 +*none*: 0x2299,416 +*none*: 0x229b,417 +*none*: 0x229c,418 +*none*: 0x229e,419 +*none*: 0x229f,420 +*none*: 0x22a1,421 +ReadKeyset: 0x22a4,424 +*none*: 0x22a7,425 +*none*: 0x22a8,426 +UpdateSwitches: 0x22ad,440 +*none*: 0x22ae,441 +*none*: 0x22b0,442 +*none*: 0x22b1,443 +*none*: 0x22b4,444 +*none*: 0x22b5,445 +*none*: 0x22b6,448 +*none*: 0x22b7,449 +*none*: 0x22b8,450 +*none*: 0x22bb,453 +*none*: 0x22bc,454 +*none*: 0x24f6,897 +*none*: 0x24f9,898 +*none*: 0x24fc,899 +*none*: 0x24ff,900 +*none*: 0x2502,901 +UpdateMousePosition: 0x250b,914 +*none*: 0x250e,915 +*none*: 0x2511,918 +*none*: 0x2513,919 +*none*: 0x2515,920 +CheckProcCSB: 0x2518,923 +*none*: 0x251b,924 +*none*: 0x251c,925 +*none*: 0x251f,928 +*none*: 0x2522,929 +*none*: 0x2523,930 +CheckBurdock: 0x2526,933 +DoToneCmd: 0x247b,809 +*none*: 0x247e,810 +*none*: 0x2480,811 +*none*: 0x2483,812 +*none*: 0x2486,813 +DoStartToneCmd: 0x2489,817 +*none*: 0x248b,818 +*none*: 0x248c,819 +*none*: 0x248e,820 +*none*: 0x2491,821 +*none*: 0x2492,822 +*none*: 0x2494,823 +*none*: 0x2495,824 +*none*: 0x2497,825 +*none*: 0x2498,826 +*none*: 0x249a,827 +TransferToneCSB: 0x249d,831 +*none*: 0x24a0,832 +*none*: 0x2529,934 +*none*: 0x252a,935 +CheckMouseHardware: 0x2505,906 +*none*: 0x2508,907 + +[Common.asm,v] +ClearMiscControl1Bit: 0x20dc,310 +ChagngeMiscControl1Bit: 0x20de,314 +*none*: 0x20e0,315 +*none*: 0x20e3,316 +*none*: 0x20e6,317 +SetMiscControl1Bit: 0x20e7,323 +*none*: 0x20e9,327 +Wait: 0x209b,247 +*none*: 0x209c,248 +*none*: 0x209d,250 +*none*: 0x209e,251 +*none*: 0x209f,252 +*none*: 0x20a2,253 +*none*: 0x20a3,254 +*none*: 0x20a6,256 +*none*: 0x20a7,257 +PutMPanel1: 0x20b2,272 +*none*: 0x20b3,273 +*none*: 0x20b4,274 +SaveNegativeMPValue: 0x20c0,282 +*none*: 0x20c1,283 +*none*: 0x20c2,284 +*none*: 0x20c3,285 +*none*: 0x20c4,286 +*none*: 0x20c5,287 +*none*: 0x20c6,288 +*none*: 0x20c7,289 +*none*: 0x20c8,290 +*none*: 0x20cb,291 +*none*: 0x20cc,292 +*none*: 0x20ce,293 +PutMPanelCheck: 0x20d5,298 +*none*: 0x20d6,299 +*none*: 0x20d7,300 +*none*: 0x20da,302 +*none*: 0x20d1,295 +*none*: 0x20d4,296 +DoMiscClock: 0x20ec,335 +*none*: 0x20ee,336 +*none*: 0x20ef,337 +*none*: 0x20f1,338 +*none*: 0x20f2,339 +*none*: 0x20f4,340 +*none*: 0x20be,279 +*none*: 0x20bf,280 +*none*: 0x20b7,275 +*none*: 0x20b8,276 +*none*: 0x20b9,277 +*none*: 0x20bb,278 +EnableRST: 0x2089,226 +*none*: 0x208a,227 +DisableRST: 0x2080,213 +*none*: 0x2082,214 +*none*: 0x2083,215 +*none*: 0x2084,216 +*none*: 0x2088,219 +*none*: 0x2087,218 +*none*: 0x2086,217 + +[SmallDominoBuffer.asm,v] +*none*: 0x4bf1,149 +*none*: 0x4bee,148 +*none*: 0x4beb,147 +*none*: 0x4bf4,150 +*none*: 0x4bf7,151 +*none*: 0x4bfa,152 +DoBurdockCmd: 0x4aad,310 +*none*: 0x4ab0,311 +*none*: 0x4ab3,312 +*none*: 0x4ab6,313 +*none*: 0x4ab9,315 +*none*: 0x4abc,316 +*none*: 0x4abf,317 +*none*: 0x4ac2,318 +*none*: 0x4ac5,319 +*none*: 0x4ac8,320 +*none*: 0x4ac9,321 +*none*: 0x4aca,322 +*none*: 0x4acc,323 +*none*: 0x4acd,324 +*none*: 0x4ad0,326 +*none*: 0x4ad3,327 +*none*: 0x4ad5,328 +*none*: 0x4ad8,329 +*none*: 0x4ada,330 +*none*: 0x4add,331 +*none*: 0x4adf,332 +*none*: 0x4ae2,333 +*none*: 0x4ae4,334 +*none*: 0x4ae7,335 +*none*: 0x4ae9,336 +*none*: 0x4aec,337 +*none*: 0x4aee,338 +*none*: 0x4af1,339 +UnknownBurdockCommand: 0x4b8e,433 +*none*: 0x4b8f,434 +FinishBurdockCommand: 0x4b92,438 +*none*: 0x4b95,439 +*none*: 0x4b98,440 +*none*: 0x4b9b,441 +*none*: 0x4b9e,442 +*none*: 0x4ba1,443 + +[CPSubs.asm,v] +WriteCPbuffer: 0x4839,141 +*none*: 0x483b,142 +InitCPCmd: 0x48b4,262 +*none*: 0x48b5,263 +*none*: 0x48b8,264 +*none*: 0x48b9,265 +*none*: 0x48bb,266 +*none*: 0x48bd,267 +*none*: 0x48bf,268 +*none*: 0x48c2,269 +*none*: 0x48c3,270 +*none*: 0x48c4,271 +*none*: 0x48c6,272 +*none*: 0x48c8,273 +*none*: 0x48ca,274 +*none*: 0x48cd,275 +*none*: 0x48ce,276 +*none*: 0x48cf,277 +*none*: 0x48d1,278 +*none*: 0x48d3,279 +*none*: 0x48d5,280 +*none*: 0x48d8,281 +*none*: 0x48d9,282 +*none*: 0x48da,283 +*none*: 0x48db,284 +*none*: 0x48dc,285 +*none*: 0x48de,286 +*none*: 0x48e0,287 +*none*: 0x48e2,288 +*none*: 0x48e5,289 +*none*: 0x48e6,290 +*none*: 0x48e7,291 +*none*: 0x48e8,292 +*none*: 0x48ea,293 +*none*: 0x48ec,294 +*none*: 0x48ee,295 +*none*: 0x48f1,296 +*none*: 0x48f2,297 +*none*: 0x48f4,298 +*none*: 0x48f6,299 +*none*: 0x48f8,300 +*none*: 0x48fb,301 +*none*: 0x483e,145 +*none*: 0x4840,147 +*none*: 0x483f,146 +*none*: 0x4841,148 +*none*: 0x4842,149 +WriteCPLoop: 0x4843,154 +*none*: 0x4844,155 +*none*: 0x4845,156 +*none*: 0x4846,157 +*none*: 0x4847,158 +*none*: 0x4849,159 +*none*: 0x484b,160 +*none*: 0x484d,161 +WriteCPByteNotThere: 0x48fc,305 +UpdateWriteTimeout: 0x48fd,307 +*none*: 0x4900,308 +*none*: 0x4902,309 +*none*: 0x4904,310 +*none*: 0x4905,312 +WriteTimeou: 0x4907,315 +*none*: 0x4908,316 +*none*: 0x490b,319 +*none*: 0x490e,320 +*none*: 0x4850,162 +*none*: 0x4851,163 +*none*: 0x4853,164 +*none*: 0x4855,165 +*none*: 0x4857,166 +*none*: 0x485a,167 +*none*: 0x485b,168 +*none*: 0x485c,169 +*none*: 0x485d,170 +*none*: 0x485e,171 +*none*: 0x485f,172 +*none*: 0x4862,173 +ReadCPbuffer: 0x480d,106 +*none*: 0x480f,107 +*none*: 0x4812,109 +*none*: 0x4813,110 +*none*: 0x4814,111 +*none*: 0x4815,112 +*none*: 0x4816,113 +ReadCPLoop: 0x4817,117 +*none*: 0x4819,118 +*none*: 0x481b,119 +*none*: 0x481e,120 +*none*: 0x4820,121 +*none*: 0x4823,122 +*none*: 0x4825,123 +*none*: 0x4827,124 +*none*: 0x482a,125 +*none*: 0x482c,126 +*none*: 0x482d,127 +*none*: 0x482e,128 +*none*: 0x4830,131 +*none*: 0x4831,132 +*none*: 0x4832,133 +*none*: 0x4833,134 +*none*: 0x4834,135 +*none*: 0x4835,136 +*none*: 0x4838,137 +DoNakedNotify: 0x48ac,246 +*none*: 0x48af,247 +*none*: 0x48b2,248 +ReadCPByteNotThere: 0x4911,325 +*none*: 0x4912,327 +*none*: 0x4915,328 +*none*: 0x4917,329 +*none*: 0x4919,330 +*none*: 0x491a,332 +*none*: 0x491c,335 +*none*: 0x491d,336 +*none*: 0x4920,339 +PortTimeOut: 0x4923,343 +Copy: 0x4940,380 +*none*: 0x4943,381 +*none*: 0x4944,382 +*none*: 0x4945,383 +*none*: 0x4946,384 +CopyCount: 0x4947,387 +*none*: 0x4949,388 +*none*: 0x494a,389 +*none*: 0x494d,390 +StartCPWriteDM: 0x487a,199 +*none*: 0x487c,200 +*none*: 0x4880,204 +*none*: 0x4882,205 +*none*: 0x4885,208 +*none*: 0x4887,209 +*none*: 0x4889,210 +*none*: 0x488c,211 +CheckCPDmaComplete: 0x488d,219 +*none*: 0x487f,203 +*none*: 0x488f,220 +*none*: 0x4891,221 +*none*: 0x4892,223 +*none*: 0x4894,224 +*none*: 0x4897,225 +CPDmaCompleted: 0x489a,229 +*none*: 0x489b,230 +*none*: 0x489e,231 +*none*: 0x48a0,232 +*none*: 0x48a2,233 +*none*: 0x48a4,234 +*none*: 0x48a5,235 + +[MPTask.asm,v] +MPTask: 0x252d,51 +*none*: 0x2530,52 +*none*: 0x2531,53 +*none*: 0x2534,54 +*none*: 0x2535,55 +*none*: 0x2536,56 +*none*: 0x2537,60 + +[LSEPTask.asm,v] +*none*: 0x253a,65 +*none*: 0x253d,66 +*none*: 0x253e,67 +*none*: 0x2541,69 +*none*: 0x2544,70 +*none*: 0x2545,71 + +[TTYTask.asm,v] +*none*: 0x25c5,55 +TTYOutTask: 0x26bd,270 +*none*: 0x26c0,271 +*none*: 0x26c1,272 +*none*: 0x26c4,275 +BeginTTYOutTask: 0x26c7,281 +*none*: 0x26ca,282 +*none*: 0x26cb,283 + +[FloppyTask.asm,v] +StartFloppyTask: 0x28cc,271 +*none*: 0x28cf,272 +*none*: 0x28d0,273 +*none*: 0x28d3,275 +*none*: 0x28d6,276 +*none*: 0x28d7,277 +*none*: 0x28da,279 +*none*: 0x28dd,280 +*none*: 0x28e0,283 +Disable75: 0x2ade,686 +*none*: 0x2ae0,687 +*none*: 0x2ae3,688 +*none*: 0x28e3,285 +*none*: 0x28e6,286 +*none*: 0x28e9,288 +*none*: 0x28ec,289 +*none*: 0x28ed,290 +*none*: 0x28f0,292 +*none*: 0x28f3,293 +*none*: 0x28f6,295 +*none*: 0x28f9,296 +FDCommandDisp: 0x28fc,300 +*none*: 0x28ff,301 +*none*: 0x2902,302 +CommandDispatch: 0x2918,323 +*none*: 0x291a,324 +*none*: 0x291d,325 +*none*: 0x291e,326 +*none*: 0x291f,327 +*none*: 0x2921,328 +*none*: 0x2922,330 +*none*: 0x2923,331 +*none*: 0x2924,332 +*none*: 0x2925,333 +*none*: 0x2926,336 +*none*: 0x2928,337 +*none*: 0x292b,338 +*none*: 0x292e,339 +*none*: 0x2931,340 +*none*: 0x2933,341 +*none*: 0x2936,342 +DoInitializeCmd: 0x2a99,632 +FDCReset: 0x2ae4,698 +*none*: 0x2ae6,699 +*none*: 0x2ae8,700 +*none*: 0x2aea,701 +*none*: 0x2aed,702 +*none*: 0x2af0,703 +*none*: 0x2af3,704 +*none*: 0x2af5,705 +*none*: 0x2af8,706 +*none*: 0x2afa,707 +*none*: 0x2afc,708 +WaitFDCBusy: 0x2aff,711 +*none*: 0x2b01,712 +*none*: 0x2b02,713 +*none*: 0x2b04,714 +*none*: 0x2b07,715 +*none*: 0x2b08,716 +*none*: 0x2a9c,634 +*none*: 0x2a9f,635 +*none*: 0x2aa1,636 +*none*: 0x2aa4,637 +*none*: 0x2aa7,638 +*none*: 0x2aa9,639 +*none*: 0x2aac,640 +*none*: 0x2aaf,641 +*none*: 0x2ab1,642 +*none*: 0x2ab4,643 +*none*: 0x2ab7,644 +*none*: 0x2ab9,645 +*none*: 0x2abc,647 +*none*: 0x2abf,649 +DoNOPCmd: 0x293d,359 +*none*: 0x293f,360 +*none*: 0x2941,362 +*none*: 0x2944,363 +*none*: 0x2947,365 +WaitForPortToNotifyResult: 0x294a,375 +*none*: 0x2ac5,661 +DoEscapeCmd: 0x2ac2,660 +*none*: 0x2ac7,662 +*none*: 0x2ad3,670 +DoDiskChgClr: 0x2ad0,669 +*none*: 0x2ad5,671 +*none*: 0x2ad7,672 +*none*: 0x2ad9,673 +*none*: 0x2adb,674 +*none*: 0x294d,376 +*none*: 0x294e,377 +NotifyCPResult: 0x2957,381 +*none*: 0x295a,382 +*none*: 0x295d,384 +*none*: 0x2960,385 +*none*: 0x2963,387 +*none*: 0x2966,388 +FinishCommand1: 0x2969,392 +*none*: 0x296b,393 +*none*: 0x296e,394 +*none*: 0x2971,395 +*none*: 0x2974,396 +*none*: 0x2975,397 +*none*: 0x2978,398 +*none*: 0x297b,399 +SaveFloppyTaskResumeAddressAndYield: 0x2e16,1440 +DoRecalibrateCmd: 0x2a4d,568 +*none*: 0x2a4f,569 +InitStepLoop: 0x2a51,571 +*none*: 0x2a52,572 +*none*: 0x2a54,574 +*none*: 0x2a55,575 +*none*: 0x2a58,576 +*none*: 0x2a5b,577 +*none*: 0x2a5e,581 +*none*: 0x2a5f,582 +DoRestore: 0x2a62,585 +*none*: 0x2a65,586 +*none*: 0x2a67,588 +*none*: 0x2a6a,591 +*none*: 0x2a6b,592 +*none*: 0x2a6d,593 +*none*: 0x2a70,595 +*none*: 0x2a71,596 +*none*: 0x2a73,597 +*none*: 0x2a76,599 +*none*: 0x2a79,601 +*none*: 0x2a7a,602 +*none*: 0x2a7d,603 +EndRecalibrate: 0x2a80,606 +DoReadSectorCmd: 0x297e,409 +*none*: 0x2981,410 +DoReadAddrCmd: 0x2984,412 +*none*: 0x2986,413 +*none*: 0x2989,414 +*none*: 0x298a,415 +*none*: 0x298d,416 +*none*: 0x298f,417 +DoTransfer: 0x2c05,1026 +*none*: 0x2c08,1027 +*none*: 0x2c0b,1028 +*none*: 0x2c0e,1029 +*none*: 0x2c11,1030 +*none*: 0x2c12,1031 +*none*: 0x2c15,1034 +*none*: 0x2c18,1035 +*none*: 0x2c1b,1036 +*none*: 0x2c1d,1038 +*none*: 0x2c20,1039 +*none*: 0x2c23,1040 +*none*: 0x2c26,1041 +*none*: 0x2c29,1042 +*none*: 0x2c2c,1044 +SetIOCBContext: 0x2bcc,956 +*none*: 0x2bcf,957 +*none*: 0x2bd1,958 +*none*: 0x2bd2,959 +*none*: 0x2bd3,960 +*none*: 0x2bd6,961 +*none*: 0x2bd8,962 +*none*: 0x2bd9,963 +*none*: 0x2bda,964 +*none*: 0x2bdd,965 +*none*: 0x2bdf,966 +*none*: 0x2be2,967 +*none*: 0x2be3,968 +COntextSide0: 0x2bf4,982 +*none*: 0x2bf6,983 +*none*: 0x2bf7,984 +*none*: 0x2bf8,986 +*none*: 0x2bfa,987 +*none*: 0x2bfd,988 +*none*: 0x2bfe,989 +SetContext: 0x2bff,991 +*none*: 0x2c00,992 +*none*: 0x2c01,993 +*none*: 0x2c02,994 +*none*: 0x2c04,995 +*none*: 0x2c2f,1046 +CheckDiskChange: 0x2bba,938 +ReadFDCStatusHi: 0x2b38,762 +*none*: 0x2b3a,763 +*none*: 0x2b3d,764 +*none*: 0x2b3e,765 +*none*: 0x2bbd,939 +*none*: 0x2bbe,940 +*none*: 0x2bc0,941 +*none*: 0x2c32,1047 +*none*: 0x2c33,1048 +*none*: 0x2c36,1050 +*none*: 0x2c39,1051 +*none*: 0x2c3c,1052 +*none*: 0x2c3d,1053 +*none*: 0x2c40,1054 +*none*: 0x2c43,1055 +*none*: 0x2c44,1056 +*none*: 0x2c47,1057 +*none*: 0x2c48,1058 +DoSeek: 0x2dbe,1345 +*none*: 0x2dbf,1346 +*none*: 0x2dc1,1347 +TrackToBig: 0x2dc4,1349 +SeekTrackOK: 0x2dca,1357 +*none*: 0x2dcc,1360 +*none*: 0x2dcf,1361 +*none*: 0x2dd1,1362 +*none*: 0x2dd4,1364 +*none*: 0x2dd6,1365 +*none*: 0x2dd7,1366 +*none*: 0x2dd8,1367 +SetNoPreComp: 0x2de8,1382 +*none*: 0x2dea,1383 +*none*: 0x2ded,1384 +*none*: 0x2dee,1385 +IssueSeek: 0x2de1,1374 +*none*: 0x2de2,1375 +*none*: 0x2de4,1376 +*none*: 0x2de5,1377 +*none*: 0x2de7,1378 +*none*: 0x2c4b,1060 +WaitFDCCompletion: 0x2b09,732 +*none*: 0x2b0a,733 +WaitFDC: 0x2b0d,735 +*none*: 0x2b10,736 +*none*: 0x2b11,737 +*none*: 0x2b14,738 +*none*: 0x2b17,739 +*none*: 0x2b1a,741 +*none*: 0x2b1c,742 +*none*: 0x2b1e,743 +*none*: 0x2b20,744 +*none*: 0x2b23,746 +*none*: 0x2b25,747 +*none*: 0x2b26,748 +*none*: 0x2b29,749 +*none*: 0x2b2b,750 +*none*: 0x2b2e,751 +*none*: 0x2b2f,752 +*none*: 0x2c4e,1063 +*none*: 0x2c4f,1064 +*none*: 0x2c51,1065 +*none*: 0x2c54,1067 +FinXferSetup: 0x2c57,1070 +*none*: 0x2c58,1071 +*none*: 0x2c5b,1072 +*none*: 0x2c5e,1073 +*none*: 0x2c61,1074 +*none*: 0x2c64,1075 +*none*: 0x2c67,1076 +WaitForDisk: 0x2c6d,1084 +*none*: 0x2c6e,1085 +*none*: 0x2c71,1086 +*none*: 0x2c72,1087 +*none*: 0x2c75,1088 +*none*: 0x2c78,1089 +*none*: 0x2c7b,1091 +*none*: 0x2c7e,1092 +*none*: 0x2c7f,1094 +*none*: 0x2c82,1096 +CheckDiskActive: 0x2c89,1101 +*none*: 0x2c8a,1104 +*none*: 0x2c8d,1105 +*none*: 0x2c8e,1106 +*none*: 0x2c91,1107 +*none*: 0x2c94,1109 +*none*: 0x2c95,1110 +*none*: 0x2c98,1111 +*none*: 0x2c9b,1112 +*none*: 0x2c9c,1114 +*none*: 0x2c9d,1115 +*none*: 0x2c6a,1082 +DoSectorCmd: 0x2df1,1400 +*none*: 0x2df4,1401 +ContSectorCmd: 0x2df6,1404 +*none*: 0x2df9,1405 +*none*: 0x2dfc,1406 +GoodDmaChannel;: 0x2dff,1410 +*none*: 0x2e02,1411 +*none*: 0x2e04,1415 +*none*: 0x2e06,1416 +*none*: 0x2e09,1418 +*none*: 0x2ca0,1119 +*none*: 0x2ca1,1121 +*none*: 0x2ca4,1122 +*none*: 0x2ca5,1123 +DoCPBufService: 0x2cc3,1150 +*none*: 0x2ca8,1125 +*none*: 0x2cab,1126 +*none*: 0x2cac,1127 +*none*: 0x2caf,1128 +*none*: 0x2cb0,1130 +*none*: 0x2cb1,1131 +*none*: 0x2cb4,1132 +*none*: 0x2cb7,1133 +MakeIOCBResultType2: 0x2ba2,909 +*none*: 0x2ba5,910 +*none*: 0x2ba7,911 +*none*: 0x2ba8,912 +*none*: 0x2ba9,913 +*none*: 0x2baa,915 +*none*: 0x2bab,916 +*none*: 0x2bae,918 +*none*: 0x2bad,917 +*none*: 0x2baf,919 +*none*: 0x2bb0,921 +*none*: 0x2bb3,922 +*none*: 0x2bb5,923 +*none*: 0x2bb6,924 +*none*: 0x2bb7,925 +*none*: 0x2bb8,926 +*none*: 0x2bb9,927 +*none*: 0x2e0b,1419 +*none*: 0x2e0e,1420 +FloppyIntr: 0x2d15,1214 +*none*: 0x2d16,1215 +*none*: 0x2d17,1216 +*none*: 0x2d18,1217 +*none*: 0x2d19,1219 +*none*: 0x2d1b,1220 +*none*: 0x2d1e,1221 +*none*: 0x2d1f,1222 +*none*: 0x2d21,1223 +*none*: 0x2d22,1224 +*none*: 0x2d25,1225 +*none*: 0x2d26,1226 +*none*: 0x2d29,1227 +*none*: 0x2d2c,1232 +*none*: 0x2d2f,1234 +*none*: 0x2d30,1235 +*none*: 0x2d32,1236 +GoodDmaEndCount1: 0x2d37,1239 +*none*: 0x2d39,1240 +*none*: 0x2d3c,1241 +EndDmaCheck: 0x2d43,1247 +*none*: 0x2d46,1249 +*none*: 0x2d47,1251 +*none*: 0x2d48,1253 +SectorCmdDone: 0x2d68,1276 +*none*: 0x2d6b,1277 +*none*: 0x2d6c,1278 +*none*: 0x2d6f,1279 +*none*: 0x2d72,1280 +*none*: 0x2d73,1281 +*none*: 0x2d76,1284 +*none*: 0x2d79,1285 +*none*: 0x2d7c,1286 +IncrIOPPtr: 0x2b66,809 +*none*: 0x2b67,810 +*none*: 0x2b68,811 +*none*: 0x2b69,812 +*none*: 0x2b6c,813 +*none*: 0x2b6d,814 +*none*: 0x2b6e,815 +*none*: 0x2b6f,816 +*none*: 0x2b72,817 +*none*: 0x2b75,818 +HLcmpDE: 0x2b85,839 +*none*: 0x2b86,840 +*none*: 0x2b87,841 +*none*: 0x2b88,842 +*none*: 0x2b89,843 +*none*: 0x2b8a,844 +FinIncrIOP: 0x2b7e,826 +*none*: 0x2b7f,827 +*none*: 0x2b82,828 +*none*: 0x2b83,829 +*none*: 0x2b84,830 +*none*: 0x2d7f,1287 +*none*: 0x2d80,1288 +*none*: 0x2d83,1289 +*none*: 0x2d86,1290 +*none*: 0x2d89,1291 +*none*: 0x2d8c,1292 +*none*: 0x2d8d,1293 +*none*: 0x2d96,1301 +*none*: 0x2d97,1302 +*none*: 0x2d9a,1303 +*none*: 0x2d9d,1304 +*none*: 0x2d9f,1305 +FinDiskService: 0x2da2,1307 +*none*: 0x2da4,1308 +*none*: 0x2da7,1309 +*none*: 0x2da8,1310 +*none*: 0x2da9,1311 +*none*: 0x2daa,1312 +*none*: 0x2c85,1097 +*none*: 0x2c86,1098 +*none*: 0x2cc4,1151 +*none*: 0x2cc7,1153 +*none*: 0x2cca,1154 +*none*: 0x2ccb,1155 +*none*: 0x2cd4,1164 +*none*: 0x2cd5,1166 +*none*: 0x2cd8,1167 +*none*: 0x2cdb,1168 +*none*: 0x2cdd,1169 +*none*: 0x2ce0,1170 +*none*: 0x2ce3,1171 +WaitCPDmaCompletion: 0x2ce9,1176 +*none*: 0x2cec,1177 +CPComplete: 0x2cf5,1181 +*none*: 0x2cf8,1183 +UpdateDataPCB: 0x2b3f,775 +*none*: 0x2b42,776 +*none*: 0x2b43,777 +*none*: 0x2b46,778 +*none*: 0x2b49,779 +*none*: 0x2b4c,780 +*none*: 0x2b4d,781 +*none*: 0x2b50,782 +*none*: 0x2b51,783 +*none*: 0x2b54,784 +*none*: 0x2b57,785 +*none*: 0x2b59,786 +UpdateIOPPtr: 0x2b5c,790 +*none*: 0x2b5f,791 +*none*: 0x2b62,792 +*none*: 0x2b65,793 +*none*: 0x2cf9,1185 +*none*: 0x2cfc,1186 +*none*: 0x2cfd,1187 +*none*: 0x2d00,1188 +FinCPBufSrvc: 0x2d0d,1197 +*none*: 0x2d0e,1198 +*none*: 0x2d11,1199 +*none*: 0x2d12,1200 + +[RS232CMisc.asm,v] +RS232CMiscTask: 0x2e19,192 +*none*: 0x2e1c,193 +*none*: 0x2e1d,194 +*none*: 0x2e20,196 +CheckMiscFlag: 0x2e23,204 +*none*: 0x2e26,205 +*none*: 0x2e27,206 +*none*: 0x2e2a,207 + +[RS232CPut.asm,v] +RS232CPutTask: 0x336c,91 +PutTaskQuiet: 0x33a4,167 +*none*: 0x33a5,168 +*none*: 0x33a8,169 + +[RS232CGet.asm,v] +RES232CGetTask: 0x3568,119 +GetTaskQuiet: 0x35c5,262 +*none*: 0x35c6,263 +*none*: 0x35c9,264 + +[MoonIOPCSTest.asm,v] +*none*: 0x425d,325 +*none*: 0x425e,326 +*none*: 0x4260,327 +*none*: 0x4263,328 +*none*: 0x4264,329 +*none*: 0x4265,330 +*none*: 0x4267,331 +*none*: 0x4268,332 +*none*: 0x426a,333 +*none*: 0x426c,334 +*none*: 0x426e,335 +*none*: 0x4270,336 +StartDmaIn: 0x4273,338 +*none*: 0x4275,339 +*none*: 0x4277,340 +*none*: 0x4279,341 +*none*: 0x427b,342 +*none*: 0x427e,343 +CheckCPDmaComplete: 0x44f2,775 +*none*: 0x44f5,777 +*none*: 0x44f7,778 + +[DmaSubs.asm,v] +StartFloppyChannel: 0x49e5,299 +*none*: 0x49e6,300 +*none*: 0x49e7,301 +*none*: 0x49e8,302 +*none*: 0x49e9,303 +*none*: 0x49ea,304 +*none*: 0x49eb,305 +*none*: 0x49ec,306 +*none*: 0x49ee,307 +*none*: 0x49ef,308 +*none*: 0x49f1,309 +*none*: 0x49f2,310 +*none*: 0x49f3,311 +*none*: 0x49f5,312 +*none*: 0x49f6,313 +*none*: 0x49f7,314 +*none*: 0x49f9,320 +*none*: 0x49fc,321 +*none*: 0x49fd,322 +*none*: 0x49fe,323 +*none*: 0x4a00,324 +*none*: 0x4a02,325 +*none*: 0x4a03,328 +*none*: 0x4a04,329 +*none*: 0x4a06,330 +*none*: 0x4a0b,332 +*none*: 0x4a08,331 +ReadDmaCompletion: 0x4991,208 +*none*: 0x4994,213 +*none*: 0x4992,209 +*none*: 0x4997,217 +*none*: 0x4998,218 +*none*: 0x499b,219 +*none*: 0x499c,220 +*none*: 0x499e,221 +*none*: 0x499f,222 +*none*: 0x49a0,223 +*none*: 0x49a3,224 +*none*: 0x49a4,225 +*none*: 0x49a5,226 +*none*: 0x49a6,227 +ClearDmaChannel: 0x49b5,245 +*none*: 0x49b6,246 +*none*: 0x49b7,247 +*none*: 0x49b8,248 +*none*: 0x49ba,252 +*none*: 0x49bd,256 +*none*: 0x49be,257 +*none*: 0x49c1,258 +*none*: 0x49c2,259 +*none*: 0x49c3,260 +*none*: 0x49c5,261 +*none*: 0x49c7,262 +*none*: 0x49c9,263 +*none*: 0x49ca,264 +*none*: 0x49cb,265 +*none*: 0x49cd,266 +*none*: 0x49d0,267 +*none*: 0x49d1,268 +StartCPDmaChannel: 0x494e,133 +*none*: 0x494f,134 +*none*: 0x4950,135 +*none*: 0x4951,136 +*none*: 0x4952,137 +*none*: 0x4953,138 +*none*: 0x4954,139 +*none*: 0x4955,140 +*none*: 0x4957,141 +*none*: 0x4958,142 +*none*: 0x495a,143 +*none*: 0x495b,144 +*none*: 0x495c,145 +*none*: 0x495e,146 +*none*: 0x495f,147 +*none*: 0x4960,148 +*none*: 0x4962,154 +*none*: 0x4963,155 +*none*: 0x4965,159 +*none*: 0x4968,163 +*none*: 0x4969,166 +*none*: 0x496c,167 +*none*: 0x496d,168 +*none*: 0x496f,169 +*none*: 0x4971,170 +*none*: 0x4973,171 +*none*: 0x4975,175 +*none*: 0x4974,174 +*none*: 0x4977,176 +*none*: 0x4979,177 +*none*: 0x497c,178 +*none*: 0x497d,179 + diff --git a/D/IOP/TODClock.cs b/D/IOP/TODClock.cs new file mode 100644 index 0000000..d2f51e8 --- /dev/null +++ b/D/IOP/TODClock.cs @@ -0,0 +1,311 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Threading; + +namespace D.IOP +{ + public enum TODPowerUpSetMode + { + HostTimeY2K = 0, + HostTime, + SpecificDateAndTime, + SpecificDate, + NoChange, + } + + public enum TODAccessMode + { + Read = 0x4, + Clear = 0x2, + Set = 0x1, + None = 0, + } + + public enum TODClockType + { + Read = 0, + SetA, + SetB, + SetC, + SetD, + } + + /// + /// Implements the Star's TOD clock and timing logic. + /// The Star does not use an off-the-shelf RTC chip, just a series of + /// 74LS393 dual 4-bit counters and 74LS165 shift registers + /// clocked off of a 1Hz clock -- these form a single 32-bit register + /// that counts seconds. It can be read and written by the IOP. + /// + /// This uses a Timer to provide decent real-time clock interrupts + /// independent of the speed of the running emulation. + /// + public class TODClock + { + public TODClock() + { + _lock = new ReaderWriterLockSlim(); + + // Get the timer rolling, it will tick once a second forever. + // TODO: this is not particularly accurate, timekeeping-wise. + _timer = new Timer(TimerTick, null, 1000, 1000); + + Reset(); + } + + public void Reset() + { + _lock.EnterWriteLock(); + _interrupt = false; + _lock.ExitWriteLock(); + + _todReadBit = 0; + _mode = TODAccessMode.None; + _powerLoss = false; + + PowerUpSetMode = Configuration.TODSetMode; + PowerUpSetTime = (PowerUpSetMode == TODPowerUpSetMode.SpecificDate) ? + Configuration.TODDate : Configuration.TODDateTime; + + SetTODClockInternal(); + } + + /// + /// Resets the clock to the current value specified by the + /// system configuration. + /// + public void ResetTODClockTime() + { + SetTODClockInternal(); + } + + /// + /// How to set the TOD clock when the system is started. + /// + public TODPowerUpSetMode PowerUpSetMode; + + /// + /// A specific time to set the TOD clock to + /// + public DateTime PowerUpSetTime; + + /// + /// The Interrupt flag indicates that a (soft) TOD interrupt + /// has occurred -- this is raised every time the clock ticks. + /// + public bool Interrupt + { + get + { + bool value; + + _lock.EnterReadLock(); + value = _interrupt; + _lock.ExitReadLock(); + return value; + } + } + + public bool PowerLoss + { + get + { + return _powerLoss; + } + } + + public void ClearInterrupt() + { + _lock.EnterWriteLock(); + _interrupt = false; + _lock.ExitWriteLock(); + } + + public void SetMode(TODAccessMode mode) + { + _mode = mode; + + switch(mode) + { + case TODAccessMode.Read: + _todReadBit = 0; + break; + + case TODAccessMode.Set: + // TODO: anything need to be done here? + break; + + case TODAccessMode.Clear: + _todValue = 0; + break; + } + } + + public int ReadClockBit() + { + _lock.EnterReadLock(); + // From BookKeepingTask.asm: "Bits from clock come in true, and most significant bit first." + int value = (_todValue & (0x80000000 >> (_todReadBit & 0x1f))) != 0 ? 0x40 : 0; + _lock.ExitReadLock(); + return value; + } + + public void ClockBit(TODClockType type) + { + switch(type) + { + case TODClockType.Read: + _todReadBit++; + break; + + // + // A clock to A,B,C or D causes the value + // in the corresponding byte of the tod counter to be incremented. + // A is the least-significant byte, D is the most-significant. + // + case TODClockType.SetA: + _lock.EnterWriteLock(); + _todValue = ((_todValue & 0xffffff00) | ((_todValue + 1) & 0xff)); + _powerLoss = false; + _lock.ExitWriteLock(); + break; + + case TODClockType.SetB: + _lock.EnterWriteLock(); + _todValue = ((_todValue & 0xffff00ff) | ((_todValue + 0x100) & 0xff00)); + _powerLoss = false; + _lock.ExitWriteLock(); + break; + + case TODClockType.SetC: + _lock.EnterWriteLock(); + _todValue = ((_todValue & 0xff00ffff) | ((_todValue + 0x10000) & 0xff0000)); + _powerLoss = false; + _lock.ExitWriteLock(); + break; + + case TODClockType.SetD: + _lock.EnterWriteLock(); + _todValue = ((_todValue & 0x00ffffff) | ((_todValue + 0x1000000) & 0xff000000)); + _powerLoss = false; + _lock.ExitWriteLock(); + break; + + default: + throw new NotImplementedException("Clock bit not implemented."); + } + } + + private void SetTODClockInternal() + { + _lock.EnterWriteLock(); + switch (PowerUpSetMode) + { + case TODPowerUpSetMode.HostTimeY2K: + // + // Get current time, and move date back 28 years since most Star software isn't happy with Y2K. + // This keeps the calendar in sync at least. + // + _todValue = GetXeroxTime(DateTime.Now.ToLocalTime().AddYears(-28)); + break; + + case TODPowerUpSetMode.HostTime: + // + // Set TOD clock to the current wall-clock time. + // + _todValue = GetXeroxTime(DateTime.Now.ToLocalTime()); + break; + + case TODPowerUpSetMode.SpecificDateAndTime: + // + // Set TOD clock to the specified date and time. + // + _todValue = GetXeroxTime(PowerUpSetTime); + break; + + case TODPowerUpSetMode.SpecificDate: + // + // Set TOD clock to the specified date, with the current time. + // + _todValue = GetXeroxTime(PowerUpSetTime.Add(DateTime.Now.TimeOfDay)); + break; + + case TODPowerUpSetMode.NoChange: + // + // Do nothing. + // + break; + } + _lock.ExitWriteLock(); + } + + private uint GetXeroxTime(DateTime time) + { + // + // The Star's epoch is 1/1/1901. + // + long dateTicks = time.Ticks; + long epochTicks = new DateTime(1901, 1, 1, 0, 0, 0).ToLocalTime().Ticks; + + long adjustedTicks = dateTicks - epochTicks; + + // Ticks are 100nS. + uint seconds = (uint)(adjustedTicks / 10000000); + + return seconds; + } + + private void TimerTick(object context) + { + // + // One real second has elapsed. + // We raise the interrupt flag and increment + // the clock value. + // + _lock.EnterWriteLock(); + _interrupt = true; + _todValue++; + _lock.ExitWriteLock(); + } + + private bool _interrupt; + private bool _powerLoss; + private UInt32 _todValue; + private int _todReadBit; + + private TODAccessMode _mode; + + // Timer for real-time clocking and a lock to make things + // thread-safe. The lock is probably overkill but let's do this right. + private Timer _timer; + private ReaderWriterLockSlim _lock; + } +} diff --git a/D/IOP/i8085.cs b/D/IOP/i8085.cs new file mode 100644 index 0000000..153d94c --- /dev/null +++ b/D/IOP/i8085.cs @@ -0,0 +1,2085 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System; +using System.Runtime.InteropServices; + +namespace D.IOP +{ + public enum InterruptType + { + RST7_5, + RST6_5, + RST5_5, + TRAP, + INTR, + } + + /// + /// Emulates the Intel 8085 processor. + /// + public class i8085 + { + public i8085(I8085MemoryBus mem, I8085IOBus io) + { + _mem = mem; + _io = io; + + InitializeInstructionTables(); + InitializeParityTable(); + + Reset(); + } + + public void Reset() + { + _pc = 0; + _sp = 0; + _r.AF = 0; + _r.BC = 0; + _r.DE = 0; + _r.HL = 0; + + _interruptMask = 0; + + _halted = false; + } + + /// + /// Raises the specified interrupt signal. + /// NOTE: At this time this only handles interrupts used by the Star's IOP + /// (RST7.5, 6.5, and 5.5). TRAP and INTR are held low in the IOP and are never used. + /// + /// + public void RaiseExternalInterrupt(InterruptType type) + { + // + // RST7.5 is edge-triggered, the others are level-triggered. + // Set pending interrupt bits. + // + switch(type) + { + case InterruptType.RST7_5: + _interruptMask = (byte)(_interruptMask | P7_5); + break; + + case InterruptType.RST6_5: + _interruptMask = (byte)(_interruptMask | P6_5); + break; + + case InterruptType.RST5_5: + _interruptMask = (byte)(_interruptMask | P5_5); + break; + + default: + throw new NotImplementedException(String.Format("{0} interrupt not implemented.", type)); + } + } + + public void ClearExternalInterrupt(InterruptType type) + { + // + // RST7.5 is edge-triggered, the others are level-triggered. + // Clear pending interrupt bits + // + switch (type) + { + case InterruptType.RST7_5: + throw new InvalidOperationException("Attempt to clear edge-triggered RST7.5 interrupt."); + + case InterruptType.RST6_5: + _interruptMask = (byte)(_interruptMask & ~P6_5); + break; + + case InterruptType.RST5_5: + _interruptMask = (byte)(_interruptMask & ~P5_5); + break; + + default: + throw new NotImplementedException(String.Format("{0} interrupt not implemented.", type)); + } + } + + public bool Halted + { + get { return _halted; } + } + + public byte A + { + get { return _r.A; } + } + + public byte F + { + get { return _r.F; } + } + + public byte B + { + get { return _r.B; } + } + + public byte C + { + get { return _r.C; } + } + + public byte D + { + get { return _r.D; } + } + + public byte E + { + get { return _r.E; } + } + + public byte H + { + get { return _r.H; } + } + + public byte L + { + get { return _r.L; } + } + + public ushort PC + { + get { return _pc; } + } + + public ushort SP + { + get { return _sp; } + } + + public ushort AF + { + get { return _r.AF; } + } + + public ushort BC + { + get { return _r.BC; } + } + + public ushort DE + { + get { return _r.DE; } + } + + public ushort HL + { + get { return _r.HL; } + } + + public string Disassemble(ushort address) + { + InstructionData i = _instructionData[_mem.ReadByte(address++)]; + + if (i.Size == 1) + { + return i.Mnemonic; + } + else if (i.Size == 2) + { + return String.Format(i.Mnemonic, _mem.ReadByte(address)); + } + else + { + return String.Format(i.Mnemonic, _mem.ReadWord(address)); + } + } + + /// + /// Executes a single 8085 instruction at the current PC. + /// Returns the number of clock cycles consumed by the operation. + /// + /// + public int Execute() + { + // + // Handle any pending interrupts if enabled and not masked. + // + if ((_interruptMask & IE) != 0) + { + // + // Interrupt priority is (from highest to lowest) TRAP,7.5,6.5,5.5,INTR + // (TRAP and INTR are not implemented.) + // Choose the highest one and vector to the right place. + // + if ((_interruptMask & P7_5) != 0 && (_interruptMask & I7_5) == 0) + { + Restore(0x3c); + + // Clear the IE mask + _interruptMask = (byte)(_interruptMask & ~IE); + + // The RST7.5 flip flop is reset when the interrupt is recognized. + _interruptMask = (byte)(_interruptMask & ~P7_5); + } + else if ((_interruptMask & P6_5) != 0 && (_interruptMask & I6_5) == 0) + { + Restore(0x34); + + // Clear the IE mask + _interruptMask = (byte)(_interruptMask & ~IE); + } + else if ((_interruptMask & P5_5) != 0 && (_interruptMask & I5_5) == 0) + { + Restore(0x2c); + + // Clear the IE mask + _interruptMask = (byte)(_interruptMask & ~IE); + } + } + + + InstructionData i = _instructionData[_mem.ReadByte(_pc++)]; + + ushort arg = 0; + if (i.Size == 2) + { + arg = _mem.ReadByte(_pc); + _pc++; + } + else if (i.Size == 3) + { + arg = _mem.ReadWord(_pc); + _pc += 2; + } + + return i.Executor(i.Opcode, arg) ? i.Cycles2 : i.Cycles1; + } + + // + // Instruction implementation + // + private bool ADD(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = arg; + break; + case 7: + src = _r.A; + break; + } + + int temp = _r.A + src; + // test for full-borrow + _r.F_CY = (temp & 0x100) != 0; + + // test for half-borrow + _r.F_AC = (((_r.A & 0x0f) + (src & 0x0f)) & 0x10) != 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.A = (byte)temp; + + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ADC(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = arg; + break; + case 7: + src = _r.A; + break; + } + + int temp = _r.A + src + (_r.F_CY ? 1 : 0); + + // test for half-carry + _r.F_AC = (((_r.A & 0x0f) + (src & 0x0f) + (_r.F_CY ? 1 : 0)) & 0x10) != 0; + + // test for carry + _r.F_CY = (temp & 0x100) != 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + _r.A = (byte)temp; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ACI(byte op, ushort arg) + { + int temp = _r.A + arg + (_r.F_CY ? 1 : 0); ; + + // test for half-carry + _r.F_AC = (((_r.A & 0x0f) + (arg & 0x0f) + (_r.F_CY ? 1 : 0)) & 0x10) != 0; + + // test for carry + _r.F_CY = (temp & 0x100) != 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + _r.A = (byte)temp; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ADI(byte op, ushort arg) + { + int temp = _r.A + arg; + + // test for half-carry + _r.F_AC = (((_r.A & 0x0f) + (arg & 0x0f)) & 0x10) != 0; + + // test for carry + _r.F_CY = (temp & 0x100) != 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + _r.A = (byte)temp; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ANA(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + _r.A &= (byte)src; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ANI(byte op, ushort arg) + { + _r.A &= (byte)arg; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool CALL(byte op, ushort arg) + { + Push(_pc); + _pc = arg; + + return false; + } + + /// + /// Conditional RET + /// + /// + /// + /// + private bool CALLC(byte op, ushort arg) + { + bool call = false; + + switch ((op & 0x38) >> 3) + { + case 0: // CNZ + call = !_r.F_Z; + break; + + case 1: // CZ + call = _r.F_Z; + break; + + case 2: // CNC + call = !_r.F_CY; + break; + + case 3: // CC + call = _r.F_CY; + break; + + case 4: // CPO: + call = !_r.F_P; + break; + + case 5: // CPE: + call = _r.F_P; + break; + + case 6: // CP: + call = !_r.F_S; + break; + + case 7: // CM + call = _r.F_S; + break; + } + + if (call) + { + Push(_pc); + _pc = arg; + } + + // no flags affected + + return false; + } + + private bool CMA(byte op, ushort arg) + { + _r.A = (byte)(~_r.A); + + // no flags affected + return false; + } + + private bool CMC(byte op, ushort arg) + { + _r.F_CY = !_r.F_CY; + + return false; + } + + private bool CMP(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + int temp = _r.A - src; + // test for full-borrow + _r.F_CY = (temp < 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (src & 0x0f)) < 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.F_P = _parityTable[(byte)temp]; + + return false; + } + + private bool CPI(byte op, ushort arg) + { + int temp = _r.A - (byte)arg; + // test for full-borrow + _r.F_CY = (temp < 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (arg & 0x0f)) < 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.F_P = _parityTable[(byte)temp]; + + return false; + } + + private bool DAA(byte op, ushort arg) + { + if ((_r.A & 0xf) > 9 || + _r.F_AC) + { + _r.A += 6; + } + + if (((_r.A & 0xf0) >> 4) > 9 || + _r.F_CY) + { + _r.A += (6 << 4); + } + + throw new NotImplementedException("DAA is not implemented yet."); + + return false; + } + + private bool DAD(byte op, ushort arg) + { + int addend = 0; + switch ((op & 0x30) >> 4) + { + case 0: + addend = _r.BC; + break; + + case 1: + addend = _r.DE; + break; + + case 2: + addend = _r.HL; + break; + + case 3: + addend = _sp; + break; + } + + _r.F_CY = (_r.HL + addend > 0xffff); + + _r.HL += (ushort)addend; + + return false; + } + + private bool DCR(byte op, ushort arg) + { + byte res = 0; + switch ((op & 0x38) >> 3) + { + case 0: + res = --_r.B; + break; + case 1: + res = --_r.C; + break; + case 2: + res = --_r.D; + break; + case 3: + res = --_r.E; + break; + case 4: + res = --_r.H; + break; + case 5: + res = --_r.L; + break; + case 6: + res = (byte)(_mem.ReadByte(_r.HL) - 1); + _mem.WriteByte(_r.HL, (byte)res); + break; + case 7: + res = --_r.A; + break; + } + + // carry not affected + _r.F_Z = (res == 0); + _r.F_S = ((res & 0x80) != 0); + _r.F_P = _parityTable[res]; + _r.F_AC = ((res & 0xf) == 0xf); // just subtracted 1, if low nybble is 0xf, there was a carry out. + + return false; + } + + private bool DCX(byte op, ushort arg) + { + switch ((op & 0x30) >> 4) + { + case 0: + _r.BC--; + break; + + case 1: + _r.DE--; + break; + + case 2: + _r.HL--; + break; + + case 3: + _sp--; + break; + } + + // no flags affected. + + return false; + } + + private bool DI(byte op, ushort arg) + { + // Clear the Interrupt Enable flag + _interruptMask = (byte)(_interruptMask & ~(IE)); + return false; + } + + private bool EI(byte op, ushort arg) + { + // Set the Interrupt Enable flag + _interruptMask |= (byte)IE; + return false; + } + + private bool HLT(byte op, ushort arg) + { + _halted = true; + return false; + } + + private bool IN(byte op, ushort arg) + { + _r.A = _io.In((byte)arg); + + return false; + } + + private bool INR(byte op, ushort arg) + { + byte res = 0; + switch ((op & 0x38) >> 3) + { + case 0: + res = ++_r.B; + break; + case 1: + res = ++_r.C; + break; + case 2: + res = ++_r.D; + break; + case 3: + res = ++_r.E; + break; + case 4: + res = ++_r.H; + break; + case 5: + res = ++_r.L; + break; + case 6: + res = (byte)(_mem.ReadByte(_r.HL) + 1); + _mem.WriteByte(_r.HL, (byte)res); + break; + case 7: + res = ++_r.A; + break; + } + + // carry not affected + _r.F_Z = (res == 0); + _r.F_S = ((res & 0x80) != 0); + _r.F_P = _parityTable[res]; + _r.F_AC = ((res & 0xf) == 0); // just added 1, if low nybble is zero, there was a carry out. + + return false; + } + + private bool Invalid(byte op, ushort arg) + { + // For now we'll throw + throw new InvalidOperationException( + String.Format("Invalid 8085 instruction {0:x2}", op)); + } + + private bool INX(byte op, ushort arg) + { + switch ((op & 0x30) >> 4) + { + case 0: + _r.BC++; + break; + + case 1: + _r.DE++; + break; + + case 2: + _r.HL++; + break; + + case 3: + _sp++; + break; + } + + // no flags affected. + + return false; + } + + private bool JMP(byte op, ushort arg) + { + bool test = false; + switch(op & 0x3f) + { + case 0x02: // jnz + test = !_r.F_Z; + break; + + case 0x03: // jmp + test = true; + break; + + case 0x0a: // jz + test = _r.F_Z; + break; + + case 0x12: // jnc + test = !_r.F_CY; + break; + + case 0x1a: // jc + test = _r.F_CY; + break; + + case 0x22: // jpo + test = !_r.F_P; + break; + + case 0x2a: // jpe + test = _r.F_P; + break; + + case 0x32: // jp + test = !_r.F_S; + break; + + case 0x3a: // jm + test = _r.F_S; + break; + + default: + throw new InvalidOperationException("Unhandled JMP instruction."); + } + + if (test) + { + _pc = arg; + } + + return test; + } + + private bool LDA(byte op, ushort arg) + { + _r.A = _mem.ReadByte(arg); + + return false; + } + + private bool LDAX(byte op, ushort arg) + { + switch ((op & 0x10) >> 4) + { + case 0: + _r.A = _mem.ReadByte(_r.BC); + break; + + case 1: + _r.A = _mem.ReadByte(_r.DE); + break; + } + + // no flags affected. + + return false; + } + + private bool LHLD(byte op, ushort arg) + { + _r.HL = _mem.ReadWord(arg); + + return false; + } + + private bool LXI(byte op, ushort arg) + { + switch ((op & 0x30) >> 4) + { + case 0: + _r.BC = arg; + break; + + case 1: + _r.DE = arg; + break; + + case 2: + _r.HL = arg; + break; + + case 3: + _sp = arg; + break; + } + + // no flags affected. + + return false; + } + + private bool MOV(byte op, ushort arg) + { + int src = op & 0x7; + int dst = (op >> 3) & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + switch (dst) + { + case 0: + _r.B = (byte)src; + break; + case 1: + _r.C = (byte)src; + break; + case 2: + _r.D = (byte)src; + break; + case 3: + _r.E = (byte)src; + break; + case 4: + _r.H = (byte)src; + break; + case 5: + _r.L = (byte)src; + break; + case 6: + _mem.WriteByte(_r.HL, (byte)src); + break; + case 7: + _r.A = (byte)src; + break; + } + + // No flags affected. + + return false; + } + + private bool MVI(byte op, ushort arg) + { + switch ((op & 0x38) >> 3) + { + case 0: + _r.B = (byte)arg; + break; + case 1: + _r.C = (byte)arg; + break; + case 2: + _r.D = (byte)arg; + break; + case 3: + _r.E = (byte)arg; + break; + case 4: + _r.H = (byte)arg; + break; + case 5: + _r.L = (byte)arg; + break; + case 6: + _mem.WriteByte(_r.HL, (byte)arg); + break; + case 7: + _r.A = (byte)arg; + break; + } + + // no flags affected. + return false; + } + + private bool NOP(byte op, ushort arg) + { + // Do nothing at all. + return false; + } + + private bool OUT(byte op, ushort arg) + { + _io.Out((byte)arg, _r.A); + return false; + } + + private bool ORA(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + _r.A |= (byte)src; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool ORI(byte op, ushort arg) + { + _r.A |= (byte)arg; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool PCHL(byte op, ushort arg) + { + _pc = _r.HL; + return false; + } + + private bool POP(byte op, ushort arg) + { + switch ((op & 0x30) >> 4) + { + case 0: + _r.BC = Pop(); + break; + + case 1: + _r.DE = Pop(); + break; + + case 2: + _r.HL = Pop(); + break; + + case 3: + _r.AF = Pop(); + break; + } + + // no flags affected. + return false; + } + + private bool PUSH(byte op, ushort arg) + { + switch ((op & 0x30) >> 4) + { + case 0: + Push(_r.BC); + break; + + case 1: + Push(_r.DE); + break; + + case 2: + Push(_r.HL); + break; + + case 3: + Push(_r.AF); + break; + } + + // no flags affected. + return false; + } + + private bool RAL(byte op, ushort arg) + { + bool newCarry = (_r.A & 0x80) != 0; + + _r.A = (byte)((_r.A << 1) | (_r.F_CY ? 1 : 0)); + _r.F_CY = newCarry; + + return false; + } + + private bool RAR(byte op, ushort arg) + { + bool newCarry = (_r.A & 0x01) != 0; + + _r.A = (byte)((_r.A >> 1) | (_r.F_CY ? 0x80 : 0)); + _r.F_CY = newCarry; + + return false; + } + + /// + /// Unconditional RET + /// + /// + /// + /// + private bool RET(byte op, ushort arg) + { + _pc = Pop(); + + // no flags affected + + return false; + } + + /// + /// Conditional RET + /// + /// + /// + /// + private bool RETC(byte op, ushort arg) + { + bool ret = false; + + switch ((op & 0x38) >> 3) + { + case 0: // RNZ + ret = !_r.F_Z; + break; + + case 1: // RZ + ret = _r.F_Z; + break; + + case 2: // RNC + ret = !_r.F_CY; + break; + + case 3: // RC + ret = _r.F_CY; + break; + + case 4: // RPO: + ret = !_r.F_P; + break; + + case 5: // RPE: + ret = _r.F_P; + break; + + case 6: // RP: + ret = !_r.F_S; + break; + + case 7: // RM + ret = _r.F_S; + break; + } + + if (ret) + { + _pc = Pop(); + } + + // no flags affected + + return false; + } + + private bool RIM(byte op, ushort arg) + { + _r.A = _interruptMask; + + return false; + } + + private bool RLC(byte op, ushort arg) + { + _r.F_CY = (_r.A & 0x80) != 0; + + _r.A = (byte)((_r.A << 1) | (_r.F_CY ? 1 : 0)); + + return false; + } + + private bool RRC(byte op, ushort arg) + { + _r.F_CY = (_r.A & 0x01) != 0; + + _r.A = (byte)((_r.A >> 1) | (_r.F_CY ? 0x80 : 0)); + + return false; + } + + private bool RST(byte op, ushort arg) + { + Restore((ushort)(op & 0x38)); + return false; + } + + private bool SBB(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + int temp = _r.A - src - (_r.F_CY ? 1 : 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (src & 0x0f) - (_r.F_CY ? 1 : 0)) < 0; + + // test for full-borrow + _r.F_CY = (temp < 0); + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.A = (byte)temp; + + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool SBI(byte op, ushort arg) + { + int temp = _r.A - (byte)arg - (_r.F_CY ? 1 : 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (arg & 0x0f) - (_r.F_CY ? 1 : 0)) < 0; + + // test for full-borrow + _r.F_CY = (temp < 0); + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.A = (byte)temp; + + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool SHLD(byte op, ushort arg) + { + _mem.WriteWord(arg, _r.HL); + + // no flags affected + return false; + } + + private bool SIM(byte op, ushort arg) + { + if ((_r.A & 0x8) != 0) + { + // Set new interrupt mask + _interruptMask = (byte)((_interruptMask & 0xf8) | (_r.A & 0x7)); + } + + if ((_r.A & 0x10) != 0) + { + // + // Clear pending interrupt for RST7.5 + // + _interruptMask = (byte)(_interruptMask & ~P7_5); + } + + // TODO: serial output (not used by IOP) + + return false; + } + + private bool SPHL(byte op, ushort arg) + { + _sp = _r.HL; + return false; + } + + private bool STA(byte op, ushort arg) + { + _mem.WriteByte(arg, _r.A); + + return false; + } + + private bool STAX(byte op, ushort arg) + { + switch ((op & 0x10) >> 4) + { + case 0: + _mem.WriteByte(_r.BC, _r.A); + break; + + case 1: + _mem.WriteByte(_r.DE, _r.A); + break; + } + + // no flags affected. + + return false; + } + + private bool STC(byte op, ushort arg) + { + _r.F_CY = true; + + return false; + } + + private bool SUB(byte op, ushort arg) + { + int src = op & 0x7; + + switch(src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + int temp = _r.A - src; + // test for full-borrow + _r.F_CY = (temp < 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (src & 0x0f)) < 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.A = (byte)temp; + + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool SUI(byte op, ushort arg) + { + int temp = _r.A - (byte)arg; + // test for full-borrow + _r.F_CY = (temp < 0); + + // test for half-borrow + _r.F_AC = (sbyte)((_r.A & 0x0f) - (arg & 0x0f)) < 0; + + // zero? + _r.F_Z = (byte)temp == 0; + + // negative? + _r.F_S = (temp & 0x80) != 0; + + _r.A = (byte)temp; + + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool XCHG(byte op, ushort arg) + { + ushort temp = _r.HL; + _r.HL = _r.DE; + _r.DE = temp; + + return false; + } + + private bool XRA(byte op, ushort arg) + { + int src = op & 0x7; + + switch (src) + { + case 0: + src = _r.B; + break; + case 1: + src = _r.C; + break; + case 2: + src = _r.D; + break; + case 3: + src = _r.E; + break; + case 4: + src = _r.H; + break; + case 5: + src = _r.L; + break; + case 6: + src = _mem.ReadByte(_r.HL); + break; + case 7: + src = _r.A; + break; + } + + _r.A ^= (byte)src; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool XRI(byte op, ushort arg) + { + _r.A ^= (byte)arg; + + _r.F_CY = false; + _r.F_AC = false; + _r.F_Z = _r.A == 0; + _r.F_S = (_r.A & 0x80) != 0; + _r.F_P = _parityTable[_r.A]; + + return false; + } + + private bool XTHL(byte op, ushort arg) + { + ushort temp = Pop(); + Push(_r.HL); + _r.HL = temp; + + return false; + } + + // + // Helper routines + // + private void Push(ushort v) + { + _sp -= 2; + _mem.WriteWord(_sp, v); + } + + private ushort Pop() + { + ushort v = _mem.ReadWord(_sp); + _sp += 2; + + return v; + } + + private void Restore(ushort addr) + { + Push(_pc); + _pc = addr; + } + + // Processor registers + private ushort _pc; + private ushort _sp; + private RegisterFile _r; + + /// + /// RegisterFile has an explict layout to simplify logic around + /// the 8-bit half-registers, etc. + /// + [StructLayout(LayoutKind.Explicit)] + public struct RegisterFile + { + // + // AF register pair + // + [FieldOffset(1)] + public byte A; + + [FieldOffset(0)] + public byte F; + + [FieldOffset(0)] + public ushort AF; + + // + // F flag bits + // TODO: would be faster to simply save boolean values here + // and only modify F when F is used. + // + public bool F_CY + { + get { return (F & 0x1) != 0; } + set { F = (byte)(value ? (F | 0x1) : (F & 0xfe)); } + } + + public bool F_P + { + get { return (F & 0x4) != 0; } + set { F = (byte)(value ? (F | 0x4) : (F & 0xfb)); } + } + + public bool F_AC + { + get { return (F & 0x10) != 0; } + set { F = (byte)(value ? (F | 0x10) : (F & 0xef)); } + } + + public bool F_Z + { + get { return (F & 0x40) != 0; } + set { F = (byte)(value ? (F | 0x40) : (F & 0xbf)); } + } + + public bool F_S + { + get { return (F & 0x80) != 0; } + set { F = (byte)(value ? (F | 0x80) : (F & 0x7f)); } + } + + // + // BC register pair + // + [FieldOffset(3)] + public byte B; + + [FieldOffset(2)] + public byte C; + + [FieldOffset(2)] + public ushort BC; + + // + // DE register pair + // + [FieldOffset(5)] + public byte D; + + [FieldOffset(4)] + public byte E; + + [FieldOffset(4)] + public ushort DE; + + // + // HL register pair + // + [FieldOffset(7)] + public byte H; + + [FieldOffset(6)] + public byte L; + + [FieldOffset(6)] + public ushort HL; + } + + // Interface to memory + private I8085MemoryBus _mem; + + // Interface to IO + private I8085IOBus _io; + + // Interrupt mask and control bits + private byte _interruptMask; + + // Interrupt mask bits + private const int I5_5 = 0x01; + private const int I6_5 = 0x02; + private const int I7_5 = 0x04; + private const int IE = 0x08; + private const int P5_5 = 0x10; + private const int P6_5 = 0x20; + private const int P7_5 = 0x40; + private const int SID = 0x80; + + // Whether the CPU has been halted vi HLT + private bool _halted; + + /// + /// Delegate for an instruction execution + /// + /// + private delegate bool Executor(byte op, ushort arg); + + private bool[] _parityTable; + + private void InitializeParityTable() + { + _parityTable = new bool[256]; + + for (int i = 0; i < 256; i++) + { + int oneBits = 0; + + for (int j = 1; j < 0x100; j = j << 1) + { + if ((i & j) != 0) + { + oneBits++; + } + } + + _parityTable[i] = ((oneBits % 2) == 0); + } + } + + /// + /// Represents data for a given 8085 opcode + /// + private sealed class InstructionData + { + public InstructionData(byte opcode, string mnemonic, ushort size, int cycles1, int cycles2, Executor executor) + { + Opcode = opcode; + Mnemonic = mnemonic; + Size = size; + Cycles1 = cycles1; + Cycles2 = cycles2; + Executor = executor; + } + + public InstructionData(byte opcode, string mnemonic, ushort size, int cycles, Executor executor) + { + Opcode = opcode; + Mnemonic = mnemonic; + Size = size; + Cycles1 = cycles; + Cycles2 = cycles; + Executor = executor; + } + + public readonly byte Opcode; + public readonly string Mnemonic; + public readonly ushort Size; + public readonly int Cycles1; + public readonly int Cycles2; + public readonly Executor Executor; + } + + private void InitializeInstructionTables() + { + _instructionData = new InstructionData[] { + new InstructionData(0x00, "NOP", 1, 4, new Executor(NOP)), + new InstructionData(0x01, "LXI B,${0:x4}", 3, 10, new Executor(LXI)), + new InstructionData(0x02, "STAX B", 1, 7, new Executor(STAX)), + new InstructionData(0x03, "INX B", 1, 6, new Executor(INX)), + new InstructionData(0x04, "INR B", 1, 4, new Executor(INR)), + new InstructionData(0x05, "DCR B", 1, 4, new Executor(DCR)), + new InstructionData(0x06, "MVI B,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x07, "RLC", 1, 4, new Executor(RLC)), + new InstructionData(0x08, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0x09, "DAD B", 1, 10, new Executor(DAD)), + new InstructionData(0x0a, "LDAX B", 1, 7, new Executor(LDAX)), + new InstructionData(0x0b, "DCX B", 1, 6, new Executor(DCX)), + new InstructionData(0x0c, "INR C", 1, 4, new Executor(INR)), + new InstructionData(0x0d, "DCR C", 1, 4, new Executor(DCR)), + new InstructionData(0x0e, "MVI C,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x0f, "RRC", 1, 4, new Executor(RRC)), + + new InstructionData(0x10, "Invalid", 1, 4, new Executor(Invalid)), + new InstructionData(0x11, "LXI D,${0:x4}", 3, 10, new Executor(LXI)), + new InstructionData(0x12, "STAX D", 1, 7, new Executor(STAX)), + new InstructionData(0x13, "INX D", 1, 6, new Executor(INX)), + new InstructionData(0x14, "INR D", 1, 4, new Executor(INR)), + new InstructionData(0x15, "DCR D", 1, 4, new Executor(DCR)), + new InstructionData(0x16, "MVI D,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x17, "RAL", 1, 4, new Executor(RAL)), + new InstructionData(0x18, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0x19, "DAD D", 1, 10, new Executor(DAD)), + new InstructionData(0x1a, "LDAX D", 1, 7, new Executor(LDAX)), + new InstructionData(0x1b, "DCX D", 1, 6, new Executor(DCX)), + new InstructionData(0x1c, "INR E", 1, 4, new Executor(INR)), + new InstructionData(0x1d, "DCR E", 1, 4, new Executor(DCR)), + new InstructionData(0x1e, "MVI E,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x1f, "RAR", 1, 4, new Executor(RAR)), + + new InstructionData(0x20, "RIM", 1, 4, new Executor(RIM)), + new InstructionData(0x21, "LXI H,${0:x4}", 3, 10, new Executor(LXI)), + new InstructionData(0x22, "SHLD $({0:x4})", 3, 16, new Executor(SHLD)), + new InstructionData(0x23, "INX H", 1, 6, new Executor(INX)), + new InstructionData(0x24, "INR H", 1, 4, new Executor(INR)), + new InstructionData(0x25, "DCR H", 1, 4, new Executor(DCR)), + new InstructionData(0x26, "MVI H,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x27, "DAA", 1, 4, new Executor(DAA)), + new InstructionData(0x28, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0x29, "DAD H", 1, 10, new Executor(DAD)), + new InstructionData(0x2a, "LHLD (${0:x4})", 3, 16, new Executor(LHLD)), + new InstructionData(0x2b, "DCX H", 1, 6, new Executor(DCX)), + new InstructionData(0x2c, "INR L", 1, 4, new Executor(INR)), + new InstructionData(0x2d, "DCR L", 1, 4, new Executor(DCR)), + new InstructionData(0x2e, "MVI L,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x2f, "CMA", 1, 4, new Executor(CMA)), + + new InstructionData(0x30, "SIM", 1, 4, new Executor(SIM)), + new InstructionData(0x31, "LXI SP,${0:x4}", 3, 10, new Executor(LXI)), + new InstructionData(0x32, "STA (${0:x4})", 3, 13, new Executor(STA)), + new InstructionData(0x33, "INX SP", 1, 6, new Executor(INX)), + new InstructionData(0x34, "INR M", 1, 10, new Executor(INR)), + new InstructionData(0x35, "DCR M", 1, 10, new Executor(DCR)), + new InstructionData(0x36, "MVI M,${0:x2}", 2, 10, new Executor(MVI)), + new InstructionData(0x37, "STC", 1, 4, new Executor(STC)), + new InstructionData(0x38, "Invalid", 2, 0, new Executor(Invalid)), + new InstructionData(0x39, "DAD SP", 1, 10, new Executor(DAD)), + new InstructionData(0x3a, "LDA (${0:x4})", 3, 13, new Executor(LDA)), + new InstructionData(0x3b, "DCX SP", 1, 6, new Executor(DCX)), + new InstructionData(0x3c, "INR A", 1, 4, new Executor(INR)), + new InstructionData(0x3d, "DCR A", 1, 4, new Executor(DCR)), + new InstructionData(0x3e, "MVI A,${0:x2}", 2, 7, new Executor(MVI)), + new InstructionData(0x3f, "CMC", 1, 4, new Executor(CMC)), + + new InstructionData(0x40, "MOV B,B", 1, 4, new Executor(MOV)), + new InstructionData(0x41, "MOV B,C", 1, 4, new Executor(MOV)), + new InstructionData(0x42, "MOV B,D", 1, 4, new Executor(MOV)), + new InstructionData(0x43, "MOV B,E", 1, 4, new Executor(MOV)), + new InstructionData(0x44, "MOV B,H", 1, 4, new Executor(MOV)), + new InstructionData(0x45, "MOV B,L", 1, 4, new Executor(MOV)), + new InstructionData(0x46, "MOV B,M", 1, 7, new Executor(MOV)), + new InstructionData(0x47, "MOV B,A", 1, 4, new Executor(MOV)), + new InstructionData(0x48, "MOV C,B", 1, 4, new Executor(MOV)), + new InstructionData(0x49, "MOV C,C", 1, 4, new Executor(MOV)), + new InstructionData(0x4a, "MOV C,D", 1, 4, new Executor(MOV)), + new InstructionData(0x4b, "MOV C,E", 1, 4, new Executor(MOV)), + new InstructionData(0x4c, "MOV C,H", 1, 4, new Executor(MOV)), + new InstructionData(0x4d, "MOV C,L", 1, 4, new Executor(MOV)), + new InstructionData(0x4e, "MOV C,M", 1, 7, new Executor(MOV)), + new InstructionData(0x4f, "MOV C,A", 1, 4, new Executor(MOV)), + + new InstructionData(0x50, "MOV D,B", 1, 4, new Executor(MOV)), + new InstructionData(0x51, "MOV D,C", 1, 4, new Executor(MOV)), + new InstructionData(0x52, "MOV D,D", 1, 4, new Executor(MOV)), + new InstructionData(0x53, "MOV D,E", 1, 4, new Executor(MOV)), + new InstructionData(0x54, "MOV D,H", 1, 4, new Executor(MOV)), + new InstructionData(0x55, "MOV D,L", 1, 4, new Executor(MOV)), + new InstructionData(0x56, "MOV D,M", 1, 7, new Executor(MOV)), + new InstructionData(0x57, "MOV D,A", 1, 4, new Executor(MOV)), + new InstructionData(0x58, "MOV E,B", 1, 4, new Executor(MOV)), + new InstructionData(0x59, "MOV E,C", 1, 4, new Executor(MOV)), + new InstructionData(0x5a, "MOV E,D", 1, 4, new Executor(MOV)), + new InstructionData(0x5b, "MOV E,E", 1, 4, new Executor(MOV)), + new InstructionData(0x5c, "MOV E,H", 1, 4, new Executor(MOV)), + new InstructionData(0x5d, "MOV E,L", 1, 4, new Executor(MOV)), + new InstructionData(0x5e, "MOV E,M", 1, 7, new Executor(MOV)), + new InstructionData(0x5f, "MOV E,A", 1, 4, new Executor(MOV)), + + new InstructionData(0x60, "MOV H,B", 1, 4, new Executor(MOV)), + new InstructionData(0x61, "MOV H,C", 1, 4, new Executor(MOV)), + new InstructionData(0x62, "MOV H,D", 1, 4, new Executor(MOV)), + new InstructionData(0x63, "MOV H,E", 1, 4, new Executor(MOV)), + new InstructionData(0x64, "MOV H,H", 1, 4, new Executor(MOV)), + new InstructionData(0x65, "MOV H,L", 1, 4, new Executor(MOV)), + new InstructionData(0x66, "MOV H,M", 1, 7, new Executor(MOV)), + new InstructionData(0x67, "MOV H,A", 1, 4, new Executor(MOV)), + new InstructionData(0x68, "MOV L,B", 1, 4, new Executor(MOV)), + new InstructionData(0x69, "MOV L,C", 1, 4, new Executor(MOV)), + new InstructionData(0x6a, "MOV L,D", 1, 4, new Executor(MOV)), + new InstructionData(0x6b, "MOV L,E", 1, 4, new Executor(MOV)), + new InstructionData(0x6c, "MOV L,H", 1, 4, new Executor(MOV)), + new InstructionData(0x6d, "MOV L,L", 1, 4, new Executor(MOV)), + new InstructionData(0x6e, "MOV L,M", 1, 7, new Executor(MOV)), + new InstructionData(0x6f, "MOV L,A", 1, 4, new Executor(MOV)), + + new InstructionData(0x70, "MOV M,B", 1, 7, new Executor(MOV)), + new InstructionData(0x71, "MOV M,C", 1, 7, new Executor(MOV)), + new InstructionData(0x72, "MOV M,D", 1, 7, new Executor(MOV)), + new InstructionData(0x73, "MOV M,E", 1, 7, new Executor(MOV)), + new InstructionData(0x74, "MOV M,H", 1, 7, new Executor(MOV)), + new InstructionData(0x75, "MOV M,L", 1, 7, new Executor(MOV)), + new InstructionData(0x76, "HLT", 1, 5, new Executor(HLT)), + new InstructionData(0x77, "MOV M,A", 1, 7, new Executor(MOV)), + new InstructionData(0x78, "MOV A,B", 1, 4, new Executor(MOV)), + new InstructionData(0x79, "MOV A,C", 1, 4, new Executor(MOV)), + new InstructionData(0x7a, "MOV A,D", 1, 4, new Executor(MOV)), + new InstructionData(0x7b, "MOV A,E", 1, 4, new Executor(MOV)), + new InstructionData(0x7c, "MOV A,H", 1, 4, new Executor(MOV)), + new InstructionData(0x7d, "MOV A,L", 1, 4, new Executor(MOV)), + new InstructionData(0x7e, "MOV A,M", 1, 7, new Executor(MOV)), + new InstructionData(0x7f, "MOV A,A", 1, 4, new Executor(MOV)), + + new InstructionData(0x80, "ADD B", 1, 4, new Executor(ADD)), + new InstructionData(0x81, "ADD C", 1, 4, new Executor(ADD)), + new InstructionData(0x82, "ADD D", 1, 4, new Executor(ADD)), + new InstructionData(0x83, "ADD E", 1, 4, new Executor(ADD)), + new InstructionData(0x84, "ADD H", 1, 4, new Executor(ADD)), + new InstructionData(0x85, "ADD L", 1, 4, new Executor(ADD)), + new InstructionData(0x86, "ADD M", 1, 7, new Executor(ADD)), + new InstructionData(0x87, "ADD A", 1, 4, new Executor(ADD)), + new InstructionData(0x88, "ADC B", 1, 4, new Executor(ADC)), + new InstructionData(0x89, "ADC C", 1, 4, new Executor(ADC)), + new InstructionData(0x8a, "ADC D", 1, 4, new Executor(ADC)), + new InstructionData(0x8b, "ADC E", 1, 4, new Executor(ADC)), + new InstructionData(0x8c, "ADC H", 1, 4, new Executor(ADC)), + new InstructionData(0x8d, "ADC L", 1, 4, new Executor(ADC)), + new InstructionData(0x8e, "ADC M", 1, 7, new Executor(ADC)), + new InstructionData(0x8f, "ADC A", 1, 4, new Executor(ADC)), + + new InstructionData(0x90, "SUB B", 1, 4, new Executor(SUB)), + new InstructionData(0x91, "SUB C", 1, 4, new Executor(SUB)), + new InstructionData(0x92, "SUB D", 1, 4, new Executor(SUB)), + new InstructionData(0x93, "SUB E", 1, 4, new Executor(SUB)), + new InstructionData(0x94, "SUB H", 1, 4, new Executor(SUB)), + new InstructionData(0x95, "SUB L", 1, 4, new Executor(SUB)), + new InstructionData(0x96, "SUB M", 1, 7, new Executor(SUB)), + new InstructionData(0x97, "SUB A", 1, 4, new Executor(SUB)), + new InstructionData(0x98, "SBB B", 1, 4, new Executor(SBB)), + new InstructionData(0x99, "SBB C", 1, 4, new Executor(SBB)), + new InstructionData(0x9a, "SBB D", 1, 4, new Executor(SBB)), + new InstructionData(0x9b, "SBB E", 1, 4, new Executor(SBB)), + new InstructionData(0x9c, "SBB H", 1, 4, new Executor(SBB)), + new InstructionData(0x9d, "SBB L", 1, 4, new Executor(SBB)), + new InstructionData(0x9e, "SBB M", 1, 7, new Executor(SBB)), + new InstructionData(0x9f, "SBB A", 1, 4, new Executor(SBB)), + + new InstructionData(0xa0, "ANA B", 1, 4, new Executor(ANA)), + new InstructionData(0xa1, "ANA C", 1, 4, new Executor(ANA)), + new InstructionData(0xa2, "ANA D", 1, 4, new Executor(ANA)), + new InstructionData(0xa3, "ANA E", 1, 4, new Executor(ANA)), + new InstructionData(0xa4, "ANA H", 1, 4, new Executor(ANA)), + new InstructionData(0xa5, "ANA L", 1, 4, new Executor(ANA)), + new InstructionData(0xa6, "ANA M", 1, 7, new Executor(ANA)), + new InstructionData(0xa7, "ANA A", 1, 4, new Executor(ANA)), + new InstructionData(0xa8, "XRA B", 1, 4, new Executor(XRA)), + new InstructionData(0xa9, "XRA C", 1, 4, new Executor(XRA)), + new InstructionData(0xaa, "XRA D", 1, 4, new Executor(XRA)), + new InstructionData(0xab, "XRA E", 1, 4, new Executor(XRA)), + new InstructionData(0xac, "XRA H", 1, 4, new Executor(XRA)), + new InstructionData(0xad, "XRA L", 1, 4, new Executor(XRA)), + new InstructionData(0xae, "XRA M", 1, 7, new Executor(XRA)), + new InstructionData(0xaf, "XRA A", 1, 4, new Executor(XRA)), + + new InstructionData(0xb0, "ORA B", 1, 4, new Executor(ORA)), + new InstructionData(0xb1, "ORA C", 1, 4, new Executor(ORA)), + new InstructionData(0xb2, "ORA D", 1, 4, new Executor(ORA)), + new InstructionData(0xb3, "ORA E", 1, 4, new Executor(ORA)), + new InstructionData(0xb4, "ORA H", 1, 4, new Executor(ORA)), + new InstructionData(0xb5, "ORA L", 1, 4, new Executor(ORA)), + new InstructionData(0xb6, "ORA M", 1, 7, new Executor(ORA)), + new InstructionData(0xb7, "ORA A", 1, 4, new Executor(ORA)), + new InstructionData(0xb8, "CMP B", 1, 4, new Executor(CMP)), + new InstructionData(0xb9, "CMP C", 1, 4, new Executor(CMP)), + new InstructionData(0xba, "CMP D", 1, 4, new Executor(CMP)), + new InstructionData(0xbb, "CMP E", 1, 4, new Executor(CMP)), + new InstructionData(0xbc, "CMP H", 1, 4, new Executor(CMP)), + new InstructionData(0xbd, "CMP L", 1, 4, new Executor(CMP)), + new InstructionData(0xbe, "CMP M", 1, 7, new Executor(CMP)), + new InstructionData(0xbf, "CMP A", 1, 4, new Executor(CMP)), + + new InstructionData(0xc0, "RNZ", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xc1, "POP B", 1, 10, new Executor(POP)), + new InstructionData(0xc2, "JNZ ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xc3, "JMP ${0:x4}", 3, 10, new Executor(JMP)), + new InstructionData(0xc4, "CNZ ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xc5, "PUSH B", 1, 12, new Executor(PUSH)), + new InstructionData(0xc6, "ADI ${0:x2}", 2, 7, new Executor(ADI)), + new InstructionData(0xc7, "RST 0", 1, 12, new Executor(RST)), + new InstructionData(0xc8, "RZ", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xc9, "RET", 1, 10, new Executor(RET)), + new InstructionData(0xca, "JZ ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xcb, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0xcc, "CZ ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xcd, "CALL ${0:x4}", 3, 18, new Executor(CALL)), + new InstructionData(0xce, "ACI ${0:x2}", 2, 7, new Executor(ACI)), + new InstructionData(0xcf, "RST 1", 1, 12, new Executor(RST)), + + new InstructionData(0xd0, "RNC", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xd1, "POP D", 1, 10, new Executor(POP)), + new InstructionData(0xd2, "JNC ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xd3, "OUT ${0:x2}", 2, 10, new Executor(OUT)), + new InstructionData(0xd4, "CNC ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xd5, "PUSH D", 1, 12, new Executor(PUSH)), + new InstructionData(0xd6, "SUI ${0:x2}", 2, 7, new Executor(SUI)), + new InstructionData(0xd7, "RST 2", 1, 12, new Executor(RST)), + new InstructionData(0xd8, "RC", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xd9, "Invalid", 1, 10, new Executor(Invalid)), + new InstructionData(0xda, "JC ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xdb, "IN ${0:x2}", 2, 10, new Executor(IN)), + new InstructionData(0xdc, "CC ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xdd, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0xde, "SBI ${0:x2}", 2, 7, new Executor(SBI)), + new InstructionData(0xdf, "RST 3", 1, 12, new Executor(RST)), + + new InstructionData(0xe0, "RPO", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xe1, "POP H", 1, 10, new Executor(POP)), + new InstructionData(0xe2, "JPO ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xe3, "XTHL", 1, 16, new Executor(XTHL)), + new InstructionData(0xe4, "CPO ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xe5, "PUSH H", 1, 12, new Executor(PUSH)), + new InstructionData(0xe6, "ANI ${0:x2}", 2, 7, new Executor(ANI)), + new InstructionData(0xe7, "RST 4", 1, 12, new Executor(RST)), + new InstructionData(0xe8, "RPE", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xe9, "PCHL", 1, 6, new Executor(PCHL)), + new InstructionData(0xea, "JPE ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xeb, "XCHG", 1, 4, new Executor(XCHG)), + new InstructionData(0xec, "CPE ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xed, "Invalid", 1, 0, new Executor(Invalid)), + new InstructionData(0xee, "XRI ${0:x2}", 2, 7, new Executor(XRI)), + new InstructionData(0xef, "RST 5", 1, 12, new Executor(RST)), + + new InstructionData(0xf0, "RP", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xf1, "POP PSW", 1, 10, new Executor(POP)), + new InstructionData(0xf2, "JP ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xf3, "DI", 1, 4, new Executor(DI)), + new InstructionData(0xf4, "CP ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xf5, "PUSH PSW", 1, 12, new Executor(PUSH)), + new InstructionData(0xf6, "ORI ${0:x2}", 2, 7, new Executor(ORI)), + new InstructionData(0xf7, "RST 6", 1, 12, new Executor(RST)), + new InstructionData(0xf8, "RM", 1, 12, 6, new Executor(RETC)), + new InstructionData(0xf9, "SPHL", 1, 6, new Executor(SPHL)), + new InstructionData(0xfa, "JM ${0:x4}", 3, 10, 7, new Executor(JMP)), + new InstructionData(0xfb, "EI", 1, 4, new Executor(EI)), + new InstructionData(0xfc, "CM ${0:x4}", 3, 18, 9, new Executor(CALLC)), + new InstructionData(0xfd, "Invalid", 3, 0, new Executor(Invalid)), + new InstructionData(0xfe, "CPI ${0:x2}", 2, 7, new Executor(CPI)), + new InstructionData(0xff, "RST 7", 1, 12, new Executor(RST)), + }; + } + + /// + /// Big ol' instruction table. Instruction metadata and jump table. + /// + private InstructionData[] _instructionData; + } + + +} diff --git a/D/Logging/Log.cs b/D/Logging/Log.cs new file mode 100644 index 0000000..4a3a835 --- /dev/null +++ b/D/Logging/Log.cs @@ -0,0 +1,164 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#define LOGGING_ENABLED + +using System; +using System.IO; + +namespace D.Logging +{ + /// + /// Specifies a component to specify logging for + /// + [Flags, ] + public enum LogComponent + { + None = 0, + + // IOP + IOP = 0x1, + IOPMemory = 0x2, + IOPIO = 0x4, + IOPFloppy = 0x8, + IOPMisc = 0x10, + IOPDMA = 0x20, + IOPKeyboard = 0x40, + IOPPrinter = 0x80, + + // CP + CPControl = 0x100, + CPExecution = 0x200, + CPMicrocodeLoad = 0x400, + CPTPCLoad = 0x800, + CPTask = 0x1000, + CPMap = 0x2000, + CPError = 0x4000, + CPIB = 0x8000, + CPStack = 0x10000, + CPInst = 0x20000, + + // Memory + MemoryControl = 0x100000, + MemoryAccess = 0x200000, + + // Display + DisplayControl = 0x400000, + + // Shugart controller + ShugartControl = 0x800000, + + // Ethernet + EthernetControl = 0x1000000, + HostEthernet = 0x2000000, + + // Configuration + Configuration = 0x4000000, + + All = 0x7fffffff + } + + /// + /// Specifies the type (or severity) of a given log message + /// + [Flags] + public enum LogType + { + None = 0, + Normal = 0x1, + Warning = 0x2, + Error = 0x4, + Verbose = 0x8, + All = 0x7fffffff + } + + /// + /// Provides basic functionality for logging messages of all types. + /// + public static class Log + { + static Log() + { + Enabled = false; + _components = LogComponent.None; + _type = LogType.None; + _logIndex = 0; + } + + public static LogComponent LogComponents + { + get { return _components; } + set { _components = value; } + } + + public static readonly bool Enabled; + +#if LOGGING_ENABLED + /// + /// Logs a message without specifying type/severity for terseness; + /// will not log if Type has been set to None. + /// + /// + /// + /// + public static void Write(LogComponent component, string message, params object[] args) + { + Write(LogType.Normal, component, message, args); + } + + public static void Write(LogType type, LogComponent component, string message, params object[] args) + { + if ((_type & type) != 0 && + (_components & component) != 0) + { + // + // My log has something to tell you... + // TODO: color based on type, etc. + Console.WriteLine(_logIndex.ToString() + ": " + component.ToString() + ": " + message, args); + _logIndex++; + } + } +#else + public static void Write(LogComponent component, string message, params object[] args) + { + + } + + public static void Write(LogType type, LogComponent component, string message, params object[] args) + { + + } + +#endif + + private static LogComponent _components; + private static LogType _type; + private static long _logIndex; + } +} diff --git a/D/Memory/Memory.cs b/D/Memory/Memory.cs new file mode 100644 index 0000000..4ad2ef1 --- /dev/null +++ b/D/Memory/Memory.cs @@ -0,0 +1,121 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Logging; +using System; + +namespace D.Memory +{ + /// + /// Encapsulates the physical memory, with enough of an ECC implementation + /// to allow diagnostics to pass. + /// + public class Memory + { + public Memory() + { + Reset(); + } + + public void Reset() + { + if (_memory == null || _memory.Length != Configuration.MemorySize * 1024) + { + _memory = new ushort[Configuration.MemorySize * 1024]; + _ecc = new byte[_memory.Length]; + } + else if (_memory != null) + { + Array.Clear(_memory, 0, _memory.Length); + Array.Clear(_ecc, 0, _ecc.Length); + } + } + + public int Size + { + get { return _memory.Length; } + } + + public ushort ReadWord(int address, out bool valid) + { + if (address < _memory.Length) + { + ushort memWord = _memory[address]; + valid = _ecc[address] == CalculateECCSyndrome(memWord); + return memWord; + } + else + { + valid = false; + return 0; + } + } + + public void WriteWord(int address, ushort value) + { + if (address < _memory.Length) + { + WriteECC(address, value); + _memory[address] = value; + } + else + { + if (Log.Enabled) Log.Write(LogComponent.MemoryAccess, "Write to nonexistent memory address 0x{0:x}", address); + } + } + + public void SetCheckBits(int invertBits) + { + _mctlInvert = invertBits; + } + + private byte CalculateECCSyndrome(ushort word) + { + // + // Not actually calculating an ECC syndrome here, as at the moment there are no + // plans to emulate faulty memory, or the hardware to correct it. + // + return 0; + } + + private void WriteECC(int address, ushort value) + { + // + // Write the calculated ECC syndrome value with the bits specified by MCtl<- + // inverted. + // + _ecc[address] = (byte)(CalculateECCSyndrome(value) ^ _mctlInvert); + } + + private ushort[] _memory; + private byte[] _ecc; + + private int _mctlInvert; + } +} diff --git a/D/Memory/MemoryController.cs b/D/Memory/MemoryController.cs new file mode 100644 index 0000000..9ff1425 --- /dev/null +++ b/D/Memory/MemoryController.cs @@ -0,0 +1,156 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.CP; +using D.Logging; + +namespace D.Memory +{ + public class MemoryController + { + public MemoryController() + { + _mem = new Memory(); + } + + public void Reset() + { + _mStatus = 0; + _mar = 0; + _md = 0; + _mdValid = true; + + _mem.Reset(); + } + + public ushort MStatus + { + get { return _mStatus; } + } + + public int MAR + { + get { return _mar; } + } + + // Exposing memory for debugging purposes. + public Memory DebugMemory + { + get { return _mem; } + } + + /// + /// Used for debugging. + /// + public ushort MD + { + get { return _md; } + } + + public void SetMCtl(ushort value) + { + // + // See HWRef, figure 18 (pg 55). + // This is used to test syndrome bits or clear error logs. + // + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.MemoryControl, "MCtl<-0x{0:x}", value); + + _mem.SetCheckBits((value & 0xff) >> 2); + + if ((value & 0x800) != 0) + { + // Clear error log for the task specified by bits [5..7]. + int task = (value & 0x700) >> 8; + _mStatus &= (ushort)(~(0x80 >> task)); + } + } + + public void LoadMAR(int address) + { + _mar = address; + + // + // Pre-load the memory requested, so we can return + // the original data in cases where MDR is written and MD is read in the same click. + // + _md = _mem.ReadWord(_mar, out _mdValid); + + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.MemoryAccess, "MAR<-0x{0:x5}", address); + } + + public void LoadMDR(ushort value) + { + // + // Write the word to memory + // + _mem.WriteWord(_mar, value); + + if (Log.Enabled) + { + Log.Write(LogType.Verbose, LogComponent.MemoryAccess, "MDR<-0x{0:x4}", value); + if (_mar >= 0x10000 && _mar < 0x20000) + { + Log.Write(LogType.Verbose, LogComponent.CPMap, "MAP 0x{0:x5} = {1:x4}", _mar, value); + } + } + } + + public ushort ReadMD(TaskType task, out bool valid) + { + // + // Return the data read in LoadMAR. + // + if (Log.Enabled) Log.Write(LogType.Verbose, LogComponent.MemoryAccess, "<-MD (0x{0:x4}) valid {1}", _md, _mdValid); + + valid = _mdValid; + + if (!valid) + { + if (Log.Enabled) Log.Write(LogComponent.MemoryAccess, "Read from nonexistent memory address 0x{0:x}", _mar); + _mStatus |= (ushort)((0x80 >> (int)task)); // Set error bit for task + _mStatus |= 0x100; // double-bit error. + + // TODO: set syndrome bits + } + else + { + // Clear single/double-bit errors (bits 6..7) + _mStatus &= 0xfcff; + } + return _md; + } + + private int _mar; + private ushort _md; + private bool _mdValid; + private ushort _mStatus; + + private Memory _mem; + } +} diff --git a/D/Notes/8085 code annotation.txt b/D/Notes/8085 code annotation.txt new file mode 100644 index 0000000..ab37b61 --- /dev/null +++ b/D/Notes/8085 code annotation.txt @@ -0,0 +1,15 @@ +Need to build a symbol table of sorts for 8085 code to allow mapping of ROM addresses +to source lines & symbol names in source code. + +Current thoughts: +Text file consisting of lines in the format: + +$-[length]: , + +to describe instruction mapping or label/constant value definition -> source line. +Duplicate fields may be present to describe symbols pointing to same address + +Start with ROM addresses that correspond directly to labels, see how interpolation (based on +assembly of source, perhaps?) can deal with the intermediary addresses. + + diff --git a/D/Notes/Ethernet.txt b/D/Notes/Ethernet.txt new file mode 100644 index 0000000..3b2c304 --- /dev/null +++ b/D/Notes/Ethernet.txt @@ -0,0 +1 @@ +The Ethernet hardware is not well documented; no official docs exist. This is a collection of notes gleaned from source code and experimentation. (from EtherInitial.mc) {The Laws of the Ethernet Hardware: -- EICtl¬ and EOCtl¬ can occur in any cycle. They must occur in c1 or c2 when turning off wakeups. -- ¬EIData must occur in c2. When read in c3, it retrieves the previous input word. -- EOData¬ can occur in any cycle. -- EStrobe for throwing out input packet must occur in c2. -- EStrobe for writing the data from EOData into the FIFO must occur in c1 or c3. } Values used for ctl registers // From EtherBootDLion Set[Off, 0]; Both Set[EnableTransmit, 1]; EOCtl<- Set[EnableTransmitLastWord, 3]; EOCtl<- Set[EnableTransmitDefer, 5]; EOCtl< Set[EnableReceive, 1]; EICtl<- // From EtherDLion Set[eEnableRcv, 1]; Set[eTurnOff, 2]; Set[eLocalLoop, 4]; Set[eLoopBack, 8]; Set[eOff, 0]; Set[eEnableTrn, 1]; Set[eLastWord, 2]; Set[eDefer, 4]; Set[eEnableTrnDefer, 5]; Set[eEnableTrnLastWord, 3]; EOCtl<- : Controls output EICtl<- : Controls input EStrobe : Loads FIFO EnableTransmitLastWord : "EOCtl ¬ EnableTransmitLastWord tells the hardware that no more words will be placed into the FIFO, and must occur in c1 or c2. Wakeups will not occur again until the FIFO has been emptied onto the Ethernet." Input: ------ {As each word of the packet is inputted, we check the Attention bit. Attention in this case indicates timeout or the end of the packet has been received. Since the normal exit to the receive loop is for the length to count down to zero, an Attention is an abnormal occurrence. If it has been set, we throw out the rest of the packet by doing an EStrobe. This EStrobe must occur in c2.} "<-EIData not in Cycle2 is rereading EIData in the case of a uCode PageCross." Output: ------- EStrobe: Must be in c1 or c3. Moves data from EOData<- to fifo. Loopback: --------- If loopback is specified, the fifo output is not cleared and will be read by <-EIData Status Bit (in Xerox order) -------------------------------- TurnOff' : 15 R. EvenLen : 14 R. GoodCRC : 13 R. Overrun' : 12 R. GoodAlign : 11 T. Underrun' : 10 T. Collision' : 9 RcvMode' : 8 EnableTrn : 7 LastWord : 6 EnableRcv : 5 LocalLoop : 4 Loopback : 3 DiagVideoData : 2 VideoClock : 1 DiagLineSync : 0 EICtl: Bit (xerox order) ------------------------------------ EnableRcv 15 TurnOff' 14 LocalLoop 13 LoopBack 12 EOCtl: Bit (etc) ---------------------------- EnableTrn 15 LastWord 14 Defer 13 EtherDisp (aka YIODisp) ----------------------- Pin 139 (YIODisp.1) : Hooked to "Attn," which appear to be whether any attention is needed by the receiver or transmitter. Pin 39 (YIODisp.0) : "(schematic) Must be zero for the transmitting inner loop uCode. It is also used to determine if the Option card is plugged in." Transmission: ------------- When EnableTrn is set, the microcode appears to: - Send 4 words of preamble bits (0x5555x3, 0x55d5 x1) which are strobed in as usual - From schematic "Last 2 1-bits of Preamble indicate 'PreamDet', Last 2 0-bits of Preamble indicate 'IgnorePacket'. Bits received least bit first." (Standard preamble + SFD.) - Does FIFO enqueue these, or do they get sent directly? (looks like they get enqueued like everything else) - Send data for the packet (strobed in). This appears to include the header + CRC - Assumption: Ether task must be put to sleep when FIFO is full, awoken when space is available -- how does this work? - See schematic, pg 2; ethernet requests (wakeups) generated by: TxMode & BufIR & Defer' & LastWord' (i.e. transmit on, fifo buffer not full, not deferring, not the last word) OR Defer & TickElapsed (microcode asked for the transmission to be deferred, and that deferral time has elapsed) OR RcvMode & BufOR & Purge' (i.e. rcv on, fifo data ready, not purging fifo) OR Attn (i.e. hardware has a a status to report) - For last word, LastWord is set -- this tells the hw that no more words are to be put into the FIFO; wakeups are removed until the remainder of the FIFO has been transmitted. Receiving: ---------- - How does loopback function? - LoopBack signal Appears to avoid clearing the fifo when rcv mode is enabled (see pg. 6 of schematics) - LocalLoop causes transmitter output to be fed into receiver input, this then goes back into the FIFO... Also stops transmission start by preventing IPG from being generated (end of which starts xmit machine) - What does Defer do? - Causes a wakeup after a delay; DeferCount timer is reset when EOCtl is loaded in c1 or c2, TickElapsed goes high after 51.2uS. - Inter-Packet Gap (start of next packet?) is started only after Defer has completed - What raises "Purge" - Looks like an EStrobe in C2. - How are odd-length packets denoted by the microcode? - Early (?) schematic has an OddLength bit in EOCtl, this is not present in the later (?) schematic. - When are CRCs generated? - Based on notes in the schematic, it looks like EndWithCRC is set on the last byte if loopback is not enabled. - If loopback is enabled, it appears that the CRC is generated by the software-side and is strobed in with the rest of the data, diagnostic code seems to bear this out. \ No newline at end of file diff --git a/D/Notes/IOP.txt b/D/Notes/IOP.txt new file mode 100644 index 0000000..6eb619b --- /dev/null +++ b/D/Notes/IOP.txt @@ -0,0 +1,214 @@ +IOP notes. These are gleaned from various bits of documentation and source code. +There is no official IOP documentation available. + +Hardware: +- CPU: 8085 +- FDC: WD FD1797 +- i8253 programmable interval timer +- i8251 UART +- i8257 dma controller +- Z80-SIO for RS232C/RS366 (?? not present on PCB...) + +Memory Map (see SysDefs.asm): + +$0000-$1FFF : PROM (8K) +$2000-$5FFF : RAM (16K) + +$8000-$800F : Host Addr PROM +$8010-$FFFF : Memory Mapped-I/O + +Map of PROM ICs to ROM Locations: + +3.1 : + +U129 - 537P03029 - $0000 +U130 - 537P03030 - $0800 +U131 - 537P03700 - $1000 +U132 - 537P03032 - $1800 + + +I/O addresses: + +$80-$83 - Alto PPI + +$84 - FDC Command (write) +$84 - FDC Status (read) +$85 - FDC Track register (write) +$86 - FDC Sector register (write) +$87 - FDC Data register (write, read?) + +$88 - Printer Data (read/write) +$89 - Printer Commands (write) +$89 - Printer Status (read) + +$8C - Timer Counter 0 (read/write) +$8D - Timer Counter 1 (read/write) +$8E - Timer Counter 2 (read/write) +$8F - Timer Mode (commands) (write) + +$90 - LSEP Uart Data +$91 - LSEP Uart Command +$92 - LSEP Uart Status +$94 - LSEP Timer Counter 0 + +$95 - Counter for SIO +$96 - Counter for Time Counter + +$97 - LSEP Timer Mode / Baud Rate Gen. Control Register (?) + +$98 - SIO Channel A Data Register +$99 - " B " +$9A - SIO Channel A Control Register +$9B - " B " + +$9C - RS366 register + +$A0 - $A8 - DMA Controller + +$B0 - $BF - Host PROM data (read) + + +$E0 - Keyset (read?) + +$E8 - FDC External State +$E9 - KB, MP, TOD clocks (write) +$E9 - Interrupt request bits (read) +$EA - clear TOD interrupt (write) +$EA - Keyboard data latch (read) + +$EB - CP data in (read) +$EB - CP data out (write) +$EC - CP (central processor) control register (write) +$EC - CP status (read) + +$ED - Mouse X counter (read) +$ED - Clear mouse X, Y counters (write) +$EE - Mouse Y counter (read) +$EE - Clear CP DMA Complete (write) + +$EF - Misc I/O devices (keyboard, clocks, etc.) input +$EF - Misc control (write) + +$F8-$FF - Control Store read/write: + +; Format of TPCHigh (write): TPCAddr[0:2],,TPCData[0:4]' ; Format of TPCLow (write): don't care,,TPCData[5:11]' +$FE - TPC High (inverted) +$FF - TPC Low (inverted) +$F8-$FD - 48 bits of control store, MSB -> LSB, bits inverted. + + + + +Memory-Mapped IO: +----------------- + +$80ec - CPControl - CP control register : IOPWait',,SwTAddr',,IOPAttn,,CPDmaMode,,CPDmaIn (write) +$80ec - CPStatus (read) +$80eb - CP data in (read) +$80eb - CP data out (write) + + + +CP <-> IOP comms +---------------- + +In WriteCPbyte (BootSubs.asm), addr $71b: + +WaitCPOutAck expects CPStatus to have the interrupt mask bit set after data is written, loops until this is so. + + +CP interrupts from IOP: + +from Kernel.mc: + +"The Kernel can be entered by one of two ways: Either via a breakpoint or the IOP asynchronously interrupting the CP via the IOPWait line. If entry is via a breakpoint, the kernel can be entered in any cycle (and inter-cycle state information must be preserved). IOPWait caused entry always occurs between clicks (so all state information is already saved by the CP and there is no memory state across clicks which can be lost, saved or restored). Upon entering the kernel, it interrupts the IOP and waits for a command byte. There are 3 possible commands that the IOP can specify: Refresh, ExitKernel, and ExecuteBufffer. Refresh is used by the IOP when it is writing the CS, ExitKernel causes the CP to leave the kernel task, and ExecuteBuffer causes the instructions which the IOP wrote in the buffer area to be executed. When the kernel is entered via a breakpoint, an R register (rK) must be used to hold memory data or a breakpoint ID (or an RH reg for ID), and a Link register to hold condition bits (or a breakpoint ID). When being entered via an IOPWait, no R register state need be lost (currently rK is lost) (i.e. rK and RHrK can first be saved away, then later restored. There should also be a second kind of ExitKernel command which doesn't write Mem[0]). Currently, the kernel is written assuming it can always use rK, so this register is lost in the IOPWait caused entry + also. (rK is used in the wait loop and in the overlay code which Burdock uses to read and write some registers.) " + + +Status / Control registers: + +IOP Ports: +CPControl: IOP -> CP (IOP write) ($EC) + - IOP uses this to control the CP; IOPAttn is set if the IOP needs attention from the CP? + - Bits are: (from IOP schematic, p 15): + - CPDmaIn - 0x8 + - CPDmaMode - 0x10 + - IOPAttn - 0x20 + - SwTAddr' - 0x40 + - IOPWait' - 0x80 + +CPStatus: CP -> IOP (IOP read) ($EC) + - CP reports its status to the IOP, also includes IOP status + - Bits are: (from IOP schematic, p 17): + - CPDmaComplete' - 0x1 + - CPOutIntReq' - 0x2 + - CPInIntReq' - 0x4 + - CPDmaIn' - 0x8 - From CPControl + - CPDmaMode' - 0x10 - " + - IOPAttn' - 0x20 - " + - EmuWake - 0x40 - From IOPCtl<- + - CPAttn - 0x80 - " + + +CP Ports: +IOPCtl<-: CP-> IOP (CP write) + - CP sets up communication between the IOP and the CP: + - Bits are: + - WakeMode.1 - 0x1 + - WakeMode.0 - 0x2 + - CPAttn - 0x4 + - EmuWake - 0x8 (is this actually used?) + + Per uCode and Schematic (IOP, p 15), the WakeMode bits are: + - 00 = Disabled (no wakeups) + - 01 = Input (wakeup when Input from IOP is available) + - 10 = Output (wakeup when IOP is ready for data from CP) + - 11 = Always wake up + +<-IOPStatus: IOP -> CP (CP read) + - CP gets the IOP's status + - Bits are (from IOP schematic, p 15): + - IOPReq - 0x1 - set when data available from IOP? + - WakeMode.1' - 0x2 + - WakeMode.0' - 0x4 + - CPAttn' - 0x8 + - EmuWake' = 0x10 + - IOPAttn - 0x20 + - Bits 0x40, 0x80 are always set low + + +Communication register: +- Appears to be a data register (8 bits + flow control) between the IOP and the CP; IOP gets status (interrupt?) when CP has written data to be read, + CP can get woken up when the IOP has written data (see below). + +CP Wakeups for IOP: +- If IOPInMode is set, the CP's IOP task will wake up if input is available from the IOP (in the data register) +- If IOPOutMode is set, wakeups will occur if the output data register is empty (i.e. the IOP is ready to receive a word.) +- If IOPAWmode is set (In and Out bits set) the IOP will always wake up regardless + + +Interrupts: +---------- +Three interrupt lines on the CPU are used as following: + +RST5.5 - CP Interrupt caused by CPAttn going high (also Burdock, the Alto debugging iface) +RST6.5 - RS232 interrupt +RST7.5 - Floppy Interrupt + +- Do no other devices actually cause interrupts? + +There is an interrupt status register that can be polled at i/o port 0xe9: + +- FloppyIntReq : 0x80 +- KBIntr : 0x40 +- PrinterTxRdy : 0x20 +- PrinterRxRdy : 0x10 +- MiscInt : 0x08 +- RS232Int' : 0x04 +- LSEP UART Tx' : 0x2 +- LSEP UART Rx' : 0x1 + +DMA: +--- + +The Intel 8257 DMA controller deals with DMA transfers for the FDC (Ch 0) and CP (Ch 1). Channels 2 and 3 go unused. diff --git a/D/Program.cs b/D/Program.cs new file mode 100644 index 0000000..0e230ea --- /dev/null +++ b/D/Program.cs @@ -0,0 +1,76 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System; +using System.Windows.Forms; +using D.UI; + +namespace D +{ + public class Program + { + [STAThread] + static void Main(string[] args) + { + PrintHerald(); + + // Cons up a system to run stuff on. + DSystem system = new DSystem(); + system.Reset(); + + // + // Start the UI, this will not return from ShowDialog + // until the window is closed. + // + DWindow mainWindow = new DWindow(system); + system.AttachDisplay(mainWindow); + DialogResult res = mainWindow.ShowDialog(); + + // + // Main window is now closed: shut down the system. + // Ensure the system is stopped. + // + system.StopExecution(); + + // + // Commit disks on normal exit + // + system.Shutdown(res == DialogResult.OK); + + Console.WriteLine("Goodbye..."); + } + + private static void PrintHerald() + { + Console.WriteLine("Darkstar v{0}", typeof(Program).Assembly.GetName().Version); + Console.WriteLine("(c) 2017, 2018 Living Computers: Museum+Labs"); + Console.WriteLine("Bug reports to joshd@livingcomputers.org"); + Console.WriteLine(); + } + } +} diff --git a/D/Properties/AssemblyInfo.cs b/D/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..857048c --- /dev/null +++ b/D/Properties/AssemblyInfo.cs @@ -0,0 +1,65 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Darkstar")] +[assembly: AssemblyDescription("A Xerox Star/1108 Emulator")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Living Computers: Museum+Labs")] +[assembly: AssemblyProduct("Darkstar")] +[assembly: AssemblyCopyright("Copyright © 2017, 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0590465e-1d91-4591-946e-ee3f7d82834b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// 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.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/D/Properties/Settings.Designer.cs b/D/Properties/Settings.Designer.cs new file mode 100644 index 0000000..25c2e8b --- /dev/null +++ b/D/Properties/Settings.Designer.cs @@ -0,0 +1,158 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace D.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string HardDriveImage { + get { + return ((string)(this["HardDriveImage"])); + } + set { + this["HardDriveImage"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string FloppyDriveImage { + get { + return ((string)(this["FloppyDriveImage"])); + } + set { + this["FloppyDriveImage"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool ThrottleSpeed { + get { + return ((bool)(this["ThrottleSpeed"])); + } + set { + this["ThrottleSpeed"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1")] + public uint DisplayScale { + get { + return ((uint)(this["DisplayScale"])); + } + set { + this["DisplayScale"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("2852201285")] + public ulong HostAddress { + get { + return ((ulong)(this["HostAddress"])); + } + set { + this["HostAddress"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string HostPacketInterfaceName { + get { + return ((string)(this["HostPacketInterfaceName"])); + } + set { + this["HostPacketInterfaceName"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1024")] + public uint MemorySize { + get { + return ((uint)(this["MemorySize"])); + } + set { + this["MemorySize"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("True")] + public bool SlowPhosphor { + get { + return ((bool)(this["SlowPhosphor"])); + } + set { + this["SlowPhosphor"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1979-12-10")] + public global::System.DateTime TODDateTime { + get { + return ((global::System.DateTime)(this["TODDateTime"])); + } + set { + this["TODDateTime"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("0")] + public int TODSetMode { + get { + return ((int)(this["TODSetMode"])); + } + set { + this["TODSetMode"] = value; + } + } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("1955-11-05")] + public global::System.DateTime TODDate { + get { + return ((global::System.DateTime)(this["TODDate"])); + } + set { + this["TODDate"] = value; + } + } + } +} diff --git a/D/Properties/Settings.settings b/D/Properties/Settings.settings new file mode 100644 index 0000000..7b8a3e7 --- /dev/null +++ b/D/Properties/Settings.settings @@ -0,0 +1,39 @@ + + + + + + + + + + + + True + + + 1 + + + 2852201285 + + + + + + 1024 + + + True + + + 1979-12-10 + + + 0 + + + 1955-11-05 + + + \ No newline at end of file diff --git a/D/SDL2.dll b/D/SDL2.dll new file mode 100644 index 0000000..2b7e319 Binary files /dev/null and b/D/SDL2.dll differ diff --git a/D/Scheduler.cs b/D/Scheduler.cs new file mode 100644 index 0000000..9d0d9a2 --- /dev/null +++ b/D/Scheduler.cs @@ -0,0 +1,252 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Collections.Generic; + +namespace D +{ + /// + /// The SchedulerEventCallback describes a delegate that is invoked whenever a scheduled event has + /// reached its due-date and is fired. + /// + /// The delta between the requested exec time and the actual exec time (in nsec) + /// An object containing context useful to the scheduler of the event + public delegate void SchedulerEventCallback(ulong skewNsec, object context); + + /// + /// An Event encapsulates a callback and associated context that is scheduled for a future timestamp. + /// + public class Event + { + public Event(ulong timestampNsec, object context, SchedulerEventCallback callback) + { + _timestampNsec = timestampNsec; + _context = context; + _callback = callback; + } + + /// + /// The absolute time (in nsec) to raise the event. + /// + public ulong TimestampNsec + { + get { return _timestampNsec; } + set { _timestampNsec = value; } + } + + /// + /// An object containing context to be passed to the + /// event callback. + /// + public object Context + { + get { return _context; } + set { _context = value; } + } + + /// + /// A delegate to be executed when the callback fires. + /// + public SchedulerEventCallback EventCallback + { + get { return _callback; } + } + + private ulong _timestampNsec; + private object _context; + private SchedulerEventCallback _callback; + } + + /// + /// The Scheduler class provides infrastructure for scheduling time-based hardware events + /// (for example, sector marks, or video task wakeups). + /// + /// Note that the Scheduler is not thread-safe and must only be used from the emulation thread, + /// or else things will break. This is not optimal -- having a thread-safe scheduler would make + /// it easier/cleaner to deal with asynchronous things like ethernet packets and scripting events + /// but doing so incurs about a 10% performance penalty so it's been avoided. + /// + public class Scheduler + { + public Scheduler() + { + Reset(); + } + + public ulong CurrentTimeNsec + { + get { return _currentTimeNsec; } + } + + public void Reset() + { + _schedule = new SchedulerQueue(); + _currentTimeNsec = 0; + } + + public void Clock() + { + // + // Move one system clock forward in time. + // + _currentTimeNsec += _timeStepNsec; + + // + // See if we have any events waiting to fire at this timestep. + // + while (_schedule.Top != null && _currentTimeNsec >= _schedule.Top.TimestampNsec) + { + // Pop the top event and fire the callback. + Event e = _schedule.Pop(); + e.EventCallback(_currentTimeNsec - e.TimestampNsec, e.Context); + } + } + + /// + /// Add a new event to the schedule. + /// + /// + /// + public Event Schedule(ulong timestampNsec, object context, SchedulerEventCallback callback) + { + + Event e = new Event(timestampNsec + _currentTimeNsec, context, callback); + _schedule.Push(e); + + return e; + } + + public Event Schedule(ulong timestampNsec, SchedulerEventCallback callback) + { + Event e = new Event(timestampNsec + _currentTimeNsec, null, callback); + _schedule.Push(e); + + return e; + } + + public void Cancel(Event e) + { + if (e != null) + { + _schedule.Remove(e); + } + } + + private ulong _currentTimeNsec; + + private SchedulerQueue _schedule; + + // 137nsec is approximately one central processor system clock cycle and is the time-base for + // the scheduler. + private const ulong _timeStepNsec = 137; + } + + /// + /// Provides an "ordered" queue based on timestamp -- the top of the queue is always the + /// next event to be fired; a "push" places a new event in order on the current queue. + /// + public class SchedulerQueue + { + public SchedulerQueue() + { + _queue = new LinkedList(); + } + + public Event Top + { + get + { + return _top; + } + } + + public bool Contains(Event e) + { + return _queue.Contains(e); + } + + public void Push(Event e) + { + // Degenerate case: list is empty or new entry is earlier than the head of the list. + if (_queue.Count == 0 || _top.TimestampNsec >= e.TimestampNsec) + { + _queue.AddFirst(e); + _top = e; + return; + } + + // + // Do a linear search to find the place to put this in. + // Since we maintain a sorted list with every insertion we only need to find the first entry + // that the new entry is earlier (or equal) to. + // This will likely be adequate as the queue should never get incredibly deep; a binary + // search may be more performant if this is not the case. + // + LinkedListNode current = _queue.First; + while (current != null) + { + if (current.Value.TimestampNsec >= e.TimestampNsec) + { + _queue.AddBefore(current, e); + return; + } + + current = current.Next; + } + + // Add at end + _queue.AddLast(e); + } + + public Event Pop() + { + Event e = _top; + _queue.RemoveFirst(); + + _top = _queue.First != null ? _queue.First.Value : null; + + return e; + } + + public void Remove(Event e) + { + if (_queue.Contains(e)) + { + _queue.Remove(e); + _top = _queue.First.Value; + } + } + + private LinkedList _queue; + + /// + /// The Top of the queue (null if queue is empty). + /// + private Event _top; + } +} diff --git a/D/System.cs b/D/System.cs new file mode 100644 index 0000000..0e6486c --- /dev/null +++ b/D/System.cs @@ -0,0 +1,410 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using D.CP; +using D.Display; +using D.Ethernet; +using D.IO; +using D.IOP; +using D.Memory; +using D.UI; +using System; +using System.Threading; + +namespace D +{ + + public delegate bool StepCallbackDelegate(); + + public delegate void ErrorCallbackDelegate(Exception e); + + /// + /// Defines the context for the current execution + /// (debug hooks, etc) + /// + public class SystemExecutionContext + { + public SystemExecutionContext(StepCallbackDelegate step8085, StepCallbackDelegate stepCP, StepCallbackDelegate stepMesa, ErrorCallbackDelegate error) + { + StepCallback8085 = step8085; + StepCallbackCP = stepCP; + StepCallbackMesa = stepMesa; + ErrorCallback = error; + } + + public readonly StepCallbackDelegate StepCallback8085; + public readonly StepCallbackDelegate StepCallbackCP; + public readonly StepCallbackDelegate StepCallbackMesa; + public readonly ErrorCallbackDelegate ErrorCallback; + } + + + /// + /// Encompasses the Star's hardware and provides functionality to run and debug the system. + /// + public class DSystem + { + public DSystem() + { + _scheduler = new Scheduler(); + + _cp = new CentralProcessor(this); + _iop = new IOProcessor(this); + _memoryController = new MemoryController(); + _displayController = new DisplayController(this); + _hardDrive = new SA1000Drive(this); + _shugartController = new ShugartController(this, _hardDrive); + _ethernetController = new EthernetController(this); + + try + { + _frameTimer = new FrameTimer(38.7); + } + catch + { + // Not supported on this platform. + _frameTimer = null; + } + } + + public void Reset() + { + bool wasExecuting = IsExecuting; + + // Save context and stop executing if the system is currently running. + SystemExecutionContext context = null; + if (wasExecuting) + { + context = _currentExecutionContext; + StopExecution(); + } + + // Now do the actual reset. + _cp.Reset(); + _iop.Reset(); + _displayController.Reset(); + _ethernetController.Reset(); + _hardDrive.Reset(); + _shugartController.Reset(); + // _scheduler.Reset(); + + _cpCycles = 0; + _elapsedCycles = 0; + + // Restart execution if we were running before the reset. + if (wasExecuting) + { + StartExecution(context); + } + } + + public void Shutdown(bool commitDisks) + { + _hardDrive.Save(); + } + + public bool IsExecuting + { + get { return _executionThread != null && _executionThread.IsAlive; } + } + + public SystemExecutionContext ExecutionContext + { + get { return _currentExecutionContext; } + } + + + public Scheduler Scheduler + { + get { return _scheduler; } + } + + public IOProcessor IOP + { + get { return _iop; } + } + + public CentralProcessor CP + { + get { return _cp; } + } + + public MemoryController MemoryController + { + get { return _memoryController; } + } + + public DisplayController DisplayController + { + get { return _displayController; } + } + + public ShugartController ShugartController + { + get { return _shugartController; } + } + + public SA1000Drive HardDrive + { + get { return _hardDrive; } + } + + public EthernetController EthernetController + { + get { return _ethernetController; } + } + + public DWindow Display + { + get { return _display; } + } + + /// + /// Allows UI to be alerted when execution state changes + /// + public delegate void ExecutionStateChangedDelegate(); + + public ExecutionStateChangedDelegate ExecutionStateChanged; + + public void AttachDisplay(DWindow display) + { + _display = display; + } + + public void StartExecution(SystemExecutionContext context) + { + StopExecution(); + + if (_executionThread == null || !_executionThread.IsAlive) + { + if (context.StepCallback8085 != null && + context.StepCallbackCP != null && + context.StepCallbackMesa != null) + { + _executionThread = new Thread(new ParameterizedThreadStart(DebugExecutionWorker)); + } + else + { + _executionThread = new Thread(new ParameterizedThreadStart(ExecutionWorker)); + } + _executionThread.Start(context); + + ExecutionStateChanged(); + + _currentExecutionContext = context; + } + } + + public void StopExecution() + { + if (_executionThread != null && _executionThread.IsAlive) + { + _abortExecution = true; + _executionThread.Join(); + + _executionThread = null; + _currentExecutionContext = null; + + ExecutionStateChanged(); + } + } + + private void DebugExecutionWorker(object obj) + { + SystemExecutionContext context = (SystemExecutionContext)obj; + + _abortExecution = false; + bool iopAbort = false; + + while (!_abortExecution) + { + try + { + // + // We clock the IOP first and let it run an instruction. + // This returns the number of clock cycles consumed -- + // on the 8085's 3Mhz timebase. + // + // We then execute the corresponding number of CP cycles + // (based on a 137ns clock period, or about 7.3Mhz) that would + // occur during that period. + // + if (_cpCycles == 0) + { + if (iopAbort) + { + // + // Out of cycles after an IOP abort, now we actually abort. + // + _abortExecution = true; + } + else + { + int i8085Cycles = _iop.Execute(); + + // This is inexact and that's probably good enough on average. + _cpCycles = (int)_cpCyclesPer8085Cycle * i8085Cycles; + + if (context.StepCallback8085()) + { + // + // Set our local iopAbort flag, we still want to continue + // to execute the CP and scheduler for as many microcycles as correspond to the + // above 8085 instruction's execution time to keep things in sync. + // + iopAbort = true; + } + } + } + + _cp.ExecuteInstruction(1); + _cpCycles--; + _elapsedCycles++; + + if (context.StepCallbackCP()) + { + // + // Break on microinstruction step + // + _abortExecution = true; + } + + if (_cp.IBDispatch && + context.StepCallbackMesa()) + { + // + // Break on macroinstruction step + // + _abortExecution = true; + } + } + catch(Exception e) + { + context.ErrorCallback(e); + _abortExecution = true; + } + } + + _cpCycles = 0; + + ExecutionStateChanged(); + } + + private void ExecutionWorker(object obj) + { + SystemExecutionContext context = (SystemExecutionContext)obj; + + _abortExecution = false; + + while (!_abortExecution) + { + try + { + // + // We clock the IOP first and let it run an instruction. + // This returns the number of clock cycles consumed -- + // on the 8085's 3Mhz timebase. + // + // We then execute the corresponding number of CP cycles + // (based on a 137ns clock period, or about 7.3Mhz) that would + // occur during that period. + // + int i8085Cycles = _iop.Execute(); + + // This is inexact and that's probably good enough on average. + _cpCycles = (int)_cpCyclesPer8085Cycle * i8085Cycles; + + _cp.ExecuteInstruction(_cpCycles); + + if (Configuration.ThrottleSpeed) + { + _elapsedCycles += _cpCycles; + + if (_elapsedCycles > _cpCyclesPerField) + { + _elapsedCycles -= _cpCyclesPerField; + + if (_frameTimer != null) + { + _frameTimer.WaitForFrame(); + } + } + } + } + catch (Exception e) + { + context.ErrorCallback(e); + _abortExecution = true; + } + } + + ExecutionStateChanged(); + } + + // + // Devices belonging to this system + // + private IOProcessor _iop; + private CentralProcessor _cp; + private MemoryController _memoryController; + private DisplayController _displayController; + private ShugartController _shugartController; + private EthernetController _ethernetController; + private SA1000Drive _hardDrive; + + // + // Display for rendering + // + private DWindow _display; + + // + // System scheduler + private Scheduler _scheduler; + + // + // Execution state + // + private Thread _executionThread; + private bool _abortExecution; + private int _cpCycles; + private double _elapsedCycles; + private SystemExecutionContext _currentExecutionContext; + + // + // Constants + // + + // Ratio of CP cycles per 8085 cycle (appx. 7.3Mhz to 3.0Mhz) + private const double _cpCyclesPer8085Cycle = 2.43 * 2.0; + + // Cycles per display field + private const double _cpCyclesPerField = 7299270.1 / 38.7; + + private FrameTimer _frameTimer; + } +} diff --git a/D/UI/AboutBox.Designer.cs b/D/UI/AboutBox.Designer.cs new file mode 100644 index 0000000..adcdd55 --- /dev/null +++ b/D/UI/AboutBox.Designer.cs @@ -0,0 +1,147 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.UI +{ + partial class AboutBox + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.DarkstarVersion = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.emailLink = new System.Windows.Forms.LinkLabel(); + this.SuspendLayout(); + // + // DarkstarVersion + // + this.DarkstarVersion.AutoSize = true; + this.DarkstarVersion.Location = new System.Drawing.Point(128, 9); + this.DarkstarVersion.Name = "DarkstarVersion"; + this.DarkstarVersion.Size = new System.Drawing.Size(144, 13); + this.DarkstarVersion.TabIndex = 0; + this.DarkstarVersion.Text = "Darkstar Version Placeholder"; + this.DarkstarVersion.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(96, 31); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(139, 13); + this.label2.TabIndex = 1; + this.label2.Text = "A Xerox Star/1108 Emulator"; + this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(56, 54); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(235, 13); + this.label3.TabIndex = 2; + this.label3.Text = "(c) 2017, 2018 Living Computers: Museum+Labs"; + this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + // + // label4 + // + this.label4.Location = new System.Drawing.Point(67, 79); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(224, 18); + this.label4.TabIndex = 5; + this.label4.Text = "Bug reports, comments and miscellanea to"; + // + // emailLink + // + this.emailLink.AutoSize = true; + this.emailLink.Location = new System.Drawing.Point(101, 97); + this.emailLink.Name = "emailLink"; + this.emailLink.Size = new System.Drawing.Size(134, 13); + this.emailLink.TabIndex = 6; + this.emailLink.TabStop = true; + this.emailLink.Text = "joshd@livingcomputers.org"; + this.emailLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.emailLink_LinkClicked); + // + // AboutBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(350, 126); + this.Controls.Add(this.emailLink); + this.Controls.Add(this.label4); + this.Controls.Add(this.label3); + this.Controls.Add(this.label2); + this.Controls.Add(this.DarkstarVersion); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Margin = new System.Windows.Forms.Padding(2); + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "AboutBox"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "About Darkstar"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label DarkstarVersion; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.LinkLabel emailLink; + } +} \ No newline at end of file diff --git a/D/UI/AboutBox.cs b/D/UI/AboutBox.cs new file mode 100644 index 0000000..6e19346 --- /dev/null +++ b/D/UI/AboutBox.cs @@ -0,0 +1,48 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +using System.Reflection; +using System.Windows.Forms; + +namespace D.UI +{ + public partial class AboutBox : Form + { + public AboutBox() + { + InitializeComponent(); + + DarkstarVersion.Text = string.Format("Darkstar v{0}", typeof(Program).Assembly.GetName().Version); + } + + private void emailLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("mailto:joshd@livingcomputers.org"); + } + } +} diff --git a/D/UI/AboutBox.resx b/D/UI/AboutBox.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/UI/AboutBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/UI/ConfigurationDialog.Designer.cs b/D/UI/ConfigurationDialog.Designer.cs new file mode 100644 index 0000000..aef4625 --- /dev/null +++ b/D/UI/ConfigurationDialog.Designer.cs @@ -0,0 +1,427 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +namespace D.UI +{ + partial class ConfigurationDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.TabControl = new System.Windows.Forms.TabControl(); + this.SystemPage = new System.Windows.Forms.TabPage(); + this.ThrottleSpeedCheckBox = new System.Windows.Forms.CheckBox(); + this.HostIDTextBox = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.MemorySizeComboBox = new System.Windows.Forms.ComboBox(); + this.label1 = new System.Windows.Forms.Label(); + this.EthernetTab = new System.Windows.Forms.TabPage(); + this.EthernetInterfaceListBox = new System.Windows.Forms.ListBox(); + this.label5 = new System.Windows.Forms.Label(); + this.DisplayTab = new System.Windows.Forms.TabPage(); + this.DisplayScaleComboBox = new System.Windows.Forms.ComboBox(); + this.label3 = new System.Windows.Forms.Label(); + this.SlowPhosphorCheckBox = new System.Windows.Forms.CheckBox(); + this.TimeTabPage = new System.Windows.Forms.TabPage(); + this.TODDateTimePicker = new System.Windows.Forms.DateTimePicker(); + this.SpecifiedTimeDateRadioButton = new System.Windows.Forms.RadioButton(); + this.CurrentTimeDateY2KRadioButton = new System.Windows.Forms.RadioButton(); + this.CurrentTimeDateRadioButton = new System.Windows.Forms.RadioButton(); + this.label4 = new System.Windows.Forms.Label(); + this.OKButton = new System.Windows.Forms.Button(); + this.Cancel_Button = new System.Windows.Forms.Button(); + this.NoTimeDateChangeRadioButton = new System.Windows.Forms.RadioButton(); + this.SpecifiedDateRadioButton = new System.Windows.Forms.RadioButton(); + this.TODDatePicker = new System.Windows.Forms.DateTimePicker(); + this.TabControl.SuspendLayout(); + this.SystemPage.SuspendLayout(); + this.EthernetTab.SuspendLayout(); + this.DisplayTab.SuspendLayout(); + this.TimeTabPage.SuspendLayout(); + this.SuspendLayout(); + // + // TabControl + // + this.TabControl.Controls.Add(this.SystemPage); + this.TabControl.Controls.Add(this.EthernetTab); + this.TabControl.Controls.Add(this.DisplayTab); + this.TabControl.Controls.Add(this.TimeTabPage); + this.TabControl.Location = new System.Drawing.Point(7, 8); + this.TabControl.Name = "TabControl"; + this.TabControl.SelectedIndex = 0; + this.TabControl.Size = new System.Drawing.Size(356, 195); + this.TabControl.TabIndex = 0; + // + // SystemPage + // + this.SystemPage.Controls.Add(this.ThrottleSpeedCheckBox); + this.SystemPage.Controls.Add(this.HostIDTextBox); + this.SystemPage.Controls.Add(this.label2); + this.SystemPage.Controls.Add(this.MemorySizeComboBox); + this.SystemPage.Controls.Add(this.label1); + this.SystemPage.Location = new System.Drawing.Point(4, 22); + this.SystemPage.Name = "SystemPage"; + this.SystemPage.Padding = new System.Windows.Forms.Padding(3); + this.SystemPage.Size = new System.Drawing.Size(348, 169); + this.SystemPage.TabIndex = 0; + this.SystemPage.Text = "System"; + this.SystemPage.UseVisualStyleBackColor = true; + // + // ThrottleSpeedCheckBox + // + this.ThrottleSpeedCheckBox.AutoSize = true; + this.ThrottleSpeedCheckBox.Location = new System.Drawing.Point(9, 53); + this.ThrottleSpeedCheckBox.Name = "ThrottleSpeedCheckBox"; + this.ThrottleSpeedCheckBox.Size = new System.Drawing.Size(146, 17); + this.ThrottleSpeedCheckBox.TabIndex = 4; + this.ThrottleSpeedCheckBox.Text = "Throttle Execution Speed"; + this.ThrottleSpeedCheckBox.UseVisualStyleBackColor = true; + // + // HostIDTextBox + // + this.HostIDTextBox.Location = new System.Drawing.Point(133, 27); + this.HostIDTextBox.Name = "HostIDTextBox"; + this.HostIDTextBox.Size = new System.Drawing.Size(98, 20); + this.HostIDTextBox.TabIndex = 3; + this.HostIDTextBox.Validating += new System.ComponentModel.CancelEventHandler(this.OnHostIDValidating); + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(6, 30); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(119, 13); + this.label2.TabIndex = 2; + this.label2.Text = "Host ID (MAC Address):"; + // + // MemorySizeComboBox + // + this.MemorySizeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.MemorySizeComboBox.FormattingEnabled = true; + this.MemorySizeComboBox.Items.AddRange(new object[] { + "1024", + "768", + "512", + "256", + "128"}); + this.MemorySizeComboBox.Location = new System.Drawing.Point(110, 3); + this.MemorySizeComboBox.Name = "MemorySizeComboBox"; + this.MemorySizeComboBox.Size = new System.Drawing.Size(121, 21); + this.MemorySizeComboBox.TabIndex = 1; + this.MemorySizeComboBox.SelectionChangeCommitted += new System.EventHandler(this.OnMemorySizeChanged); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(6, 7); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(97, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Memory Size (KW):"; + // + // EthernetTab + // + this.EthernetTab.Controls.Add(this.EthernetInterfaceListBox); + this.EthernetTab.Controls.Add(this.label5); + this.EthernetTab.Location = new System.Drawing.Point(4, 22); + this.EthernetTab.Name = "EthernetTab"; + this.EthernetTab.Padding = new System.Windows.Forms.Padding(3); + this.EthernetTab.Size = new System.Drawing.Size(348, 169); + this.EthernetTab.TabIndex = 1; + this.EthernetTab.Text = "Ethernet"; + this.EthernetTab.UseVisualStyleBackColor = true; + // + // EthernetInterfaceListBox + // + this.EthernetInterfaceListBox.FormattingEnabled = true; + this.EthernetInterfaceListBox.Location = new System.Drawing.Point(10, 24); + this.EthernetInterfaceListBox.Name = "EthernetInterfaceListBox"; + this.EthernetInterfaceListBox.Size = new System.Drawing.Size(332, 134); + this.EthernetInterfaceListBox.TabIndex = 1; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(7, 7); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(240, 13); + this.label5.TabIndex = 0; + this.label5.Text = "Select the network interface to use with Darkstar:"; + // + // DisplayTab + // + this.DisplayTab.Controls.Add(this.DisplayScaleComboBox); + this.DisplayTab.Controls.Add(this.label3); + this.DisplayTab.Controls.Add(this.SlowPhosphorCheckBox); + this.DisplayTab.Location = new System.Drawing.Point(4, 22); + this.DisplayTab.Name = "DisplayTab"; + this.DisplayTab.Padding = new System.Windows.Forms.Padding(3); + this.DisplayTab.Size = new System.Drawing.Size(348, 169); + this.DisplayTab.TabIndex = 2; + this.DisplayTab.Text = "Display"; + this.DisplayTab.UseVisualStyleBackColor = true; + // + // DisplayScaleComboBox + // + this.DisplayScaleComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.DisplayScaleComboBox.FormattingEnabled = true; + this.DisplayScaleComboBox.Items.AddRange(new object[] { + "1x", + "2x", + "4x"}); + this.DisplayScaleComboBox.Location = new System.Drawing.Point(84, 28); + this.DisplayScaleComboBox.Name = "DisplayScaleComboBox"; + this.DisplayScaleComboBox.Size = new System.Drawing.Size(55, 21); + this.DisplayScaleComboBox.TabIndex = 2; + this.DisplayScaleComboBox.SelectionChangeCommitted += new System.EventHandler(this.OnDisplayScaleChanged); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(7, 31); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(71, 13); + this.label3.TabIndex = 1; + this.label3.Text = "Display Scale"; + // + // SlowPhosphorCheckBox + // + this.SlowPhosphorCheckBox.AutoSize = true; + this.SlowPhosphorCheckBox.Location = new System.Drawing.Point(7, 7); + this.SlowPhosphorCheckBox.Name = "SlowPhosphorCheckBox"; + this.SlowPhosphorCheckBox.Size = new System.Drawing.Size(148, 17); + this.SlowPhosphorCheckBox.TabIndex = 0; + this.SlowPhosphorCheckBox.Text = "Slow Phosphor Simulation"; + this.SlowPhosphorCheckBox.UseVisualStyleBackColor = true; + // + // TimeTabPage + // + this.TimeTabPage.Controls.Add(this.TODDatePicker); + this.TimeTabPage.Controls.Add(this.SpecifiedDateRadioButton); + this.TimeTabPage.Controls.Add(this.NoTimeDateChangeRadioButton); + this.TimeTabPage.Controls.Add(this.TODDateTimePicker); + this.TimeTabPage.Controls.Add(this.SpecifiedTimeDateRadioButton); + this.TimeTabPage.Controls.Add(this.CurrentTimeDateY2KRadioButton); + this.TimeTabPage.Controls.Add(this.CurrentTimeDateRadioButton); + this.TimeTabPage.Controls.Add(this.label4); + this.TimeTabPage.Location = new System.Drawing.Point(4, 22); + this.TimeTabPage.Name = "TimeTabPage"; + this.TimeTabPage.Padding = new System.Windows.Forms.Padding(3); + this.TimeTabPage.Size = new System.Drawing.Size(348, 169); + this.TimeTabPage.TabIndex = 3; + this.TimeTabPage.Text = "Time"; + this.TimeTabPage.UseVisualStyleBackColor = true; + // + // TODDateTimePicker + // + this.TODDateTimePicker.CustomFormat = "MM\'/\'dd\'/\'yyyy HH\':\'mm\':\'ss"; + this.TODDateTimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Custom; + this.TODDateTimePicker.Location = new System.Drawing.Point(136, 70); + this.TODDateTimePicker.Name = "TODDateTimePicker"; + this.TODDateTimePicker.ShowUpDown = true; + this.TODDateTimePicker.Size = new System.Drawing.Size(200, 20); + this.TODDateTimePicker.TabIndex = 4; + // + // SpecifiedTimeDateRadioButton + // + this.SpecifiedTimeDateRadioButton.AutoSize = true; + this.SpecifiedTimeDateRadioButton.Location = new System.Drawing.Point(10, 70); + this.SpecifiedTimeDateRadioButton.Name = "SpecifiedTimeDateRadioButton"; + this.SpecifiedTimeDateRadioButton.Size = new System.Drawing.Size(120, 17); + this.SpecifiedTimeDateRadioButton.TabIndex = 3; + this.SpecifiedTimeDateRadioButton.TabStop = true; + this.SpecifiedTimeDateRadioButton.Text = "Specified date/time:"; + this.SpecifiedTimeDateRadioButton.UseVisualStyleBackColor = true; + // + // CurrentTimeDateY2KRadioButton + // + this.CurrentTimeDateY2KRadioButton.AutoSize = true; + this.CurrentTimeDateY2KRadioButton.Location = new System.Drawing.Point(10, 47); + this.CurrentTimeDateY2KRadioButton.Name = "CurrentTimeDateY2KRadioButton"; + this.CurrentTimeDateY2KRadioButton.Size = new System.Drawing.Size(273, 17); + this.CurrentTimeDateY2KRadioButton.TabIndex = 2; + this.CurrentTimeDateY2KRadioButton.TabStop = true; + this.CurrentTimeDateY2KRadioButton.Text = "Current time/date with Y2K compensation (-28 years)"; + this.CurrentTimeDateY2KRadioButton.UseVisualStyleBackColor = true; + // + // CurrentTimeDateRadioButton + // + this.CurrentTimeDateRadioButton.AutoSize = true; + this.CurrentTimeDateRadioButton.Location = new System.Drawing.Point(10, 24); + this.CurrentTimeDateRadioButton.Name = "CurrentTimeDateRadioButton"; + this.CurrentTimeDateRadioButton.Size = new System.Drawing.Size(107, 17); + this.CurrentTimeDateRadioButton.TabIndex = 1; + this.CurrentTimeDateRadioButton.TabStop = true; + this.CurrentTimeDateRadioButton.Text = "Current time/date"; + this.CurrentTimeDateRadioButton.UseVisualStyleBackColor = true; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(7, 7); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(228, 13); + this.label4.TabIndex = 0; + this.label4.Text = "At power up/reset, set emulated TOD clock to:"; + // + // OKButton + // + this.OKButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.OKButton.Location = new System.Drawing.Point(207, 209); + this.OKButton.Name = "OKButton"; + this.OKButton.Size = new System.Drawing.Size(75, 23); + this.OKButton.TabIndex = 1; + this.OKButton.Text = "OK"; + this.OKButton.UseVisualStyleBackColor = true; + // + // Cancel_Button + // + this.Cancel_Button.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.Cancel_Button.Location = new System.Drawing.Point(288, 209); + this.Cancel_Button.Name = "Cancel_Button"; + this.Cancel_Button.Size = new System.Drawing.Size(75, 23); + this.Cancel_Button.TabIndex = 2; + this.Cancel_Button.Text = "Cancel"; + this.Cancel_Button.UseVisualStyleBackColor = true; + // + // NoTimeDateChangeRadioButton + // + this.NoTimeDateChangeRadioButton.AutoSize = true; + this.NoTimeDateChangeRadioButton.Location = new System.Drawing.Point(10, 119); + this.NoTimeDateChangeRadioButton.Name = "NoTimeDateChangeRadioButton"; + this.NoTimeDateChangeRadioButton.Size = new System.Drawing.Size(205, 17); + this.NoTimeDateChangeRadioButton.TabIndex = 5; + this.NoTimeDateChangeRadioButton.TabStop = true; + this.NoTimeDateChangeRadioButton.Text = "No change (do not modify TOD clock)"; + this.NoTimeDateChangeRadioButton.UseVisualStyleBackColor = true; + // + // SpecifiedDateRadioButton + // + this.SpecifiedDateRadioButton.AutoSize = true; + this.SpecifiedDateRadioButton.Location = new System.Drawing.Point(10, 93); + this.SpecifiedDateRadioButton.Name = "SpecifiedDateRadioButton"; + this.SpecifiedDateRadioButton.Size = new System.Drawing.Size(118, 17); + this.SpecifiedDateRadioButton.TabIndex = 6; + this.SpecifiedDateRadioButton.TabStop = true; + this.SpecifiedDateRadioButton.Text = "Specified date only:"; + this.SpecifiedDateRadioButton.UseVisualStyleBackColor = true; + // + // TODDatePicker + // + this.TODDatePicker.CustomFormat = "MM\'/\'dd\'/\'yyyy"; + this.TODDatePicker.Format = System.Windows.Forms.DateTimePickerFormat.Custom; + this.TODDatePicker.Location = new System.Drawing.Point(136, 93); + this.TODDatePicker.Name = "TODDatePicker"; + this.TODDatePicker.ShowUpDown = true; + this.TODDatePicker.Size = new System.Drawing.Size(200, 20); + this.TODDatePicker.TabIndex = 7; + // + // ConfigurationDialog + // + this.AcceptButton = this.OKButton; + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(367, 238); + this.Controls.Add(this.Cancel_Button); + this.Controls.Add(this.OKButton); + this.Controls.Add(this.TabControl); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ConfigurationDialog"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "System Configuration"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.OnClosed); + this.Load += new System.EventHandler(this.OnLoad); + this.TabControl.ResumeLayout(false); + this.SystemPage.ResumeLayout(false); + this.SystemPage.PerformLayout(); + this.EthernetTab.ResumeLayout(false); + this.EthernetTab.PerformLayout(); + this.DisplayTab.ResumeLayout(false); + this.DisplayTab.PerformLayout(); + this.TimeTabPage.ResumeLayout(false); + this.TimeTabPage.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.TabControl TabControl; + private System.Windows.Forms.TabPage SystemPage; + private System.Windows.Forms.TabPage EthernetTab; + private System.Windows.Forms.TabPage DisplayTab; + private System.Windows.Forms.ComboBox MemorySizeComboBox; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox HostIDTextBox; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.ComboBox DisplayScaleComboBox; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.CheckBox SlowPhosphorCheckBox; + private System.Windows.Forms.Button OKButton; + private System.Windows.Forms.Button Cancel_Button; + private System.Windows.Forms.CheckBox ThrottleSpeedCheckBox; + private System.Windows.Forms.TabPage TimeTabPage; + private System.Windows.Forms.DateTimePicker TODDateTimePicker; + private System.Windows.Forms.RadioButton SpecifiedTimeDateRadioButton; + private System.Windows.Forms.RadioButton CurrentTimeDateY2KRadioButton; + private System.Windows.Forms.RadioButton CurrentTimeDateRadioButton; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.ListBox EthernetInterfaceListBox; + private System.Windows.Forms.RadioButton NoTimeDateChangeRadioButton; + private System.Windows.Forms.DateTimePicker TODDatePicker; + private System.Windows.Forms.RadioButton SpecifiedDateRadioButton; + } +} \ No newline at end of file diff --git a/D/UI/ConfigurationDialog.cs b/D/UI/ConfigurationDialog.cs new file mode 100644 index 0000000..ebfd039 --- /dev/null +++ b/D/UI/ConfigurationDialog.cs @@ -0,0 +1,250 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Ethernet; +using D.IOP; +using SharpPcap; +using SharpPcap.WinPcap; +using System; +using System.Windows.Forms; + +namespace D.UI +{ + public partial class ConfigurationDialog : Form + { + public ConfigurationDialog() + { + InitializeComponent(); + } + + public uint MemorySize; + public ulong HostID; + public bool ThrottleSpeed; + + public string HostPacketInterfaceName; + + public bool SlowPhosphor; + public uint DisplayScale; + + public TODPowerUpSetMode TODSetMode; + public DateTime TODDateTime; + public DateTime TODDate; + + private void PopulateUI() + { + // System Tab + MemorySizeComboBox.Items.Clear(); + for (int i = 128; i <= 1024; i+= 128) + { + MemorySizeComboBox.Items.Add(string.Format("{0}KW", i)); + if (i == MemorySize) + { + MemorySizeComboBox.SelectedIndex = MemorySizeComboBox.Items.Count - 1; + } + } + + HostIDTextBox.Text = string.Format("{0:x12}", HostID); + ThrottleSpeedCheckBox.Checked = ThrottleSpeed; + + // Ethernet Tab + PopulateNetworkAdapterList(); + + // Display Tab + DisplayScaleComboBox.Items.Clear(); + for (int i = 1; i < 5; i++) + { + DisplayScaleComboBox.Items.Add(string.Format("{0}x", i)); + if (i == DisplayScale) + { + DisplayScaleComboBox.SelectedIndex = i - 1; + } + } + + SlowPhosphorCheckBox.Checked = SlowPhosphor; + + // Time Tab + switch (TODSetMode) + { + case TODPowerUpSetMode.HostTime: + CurrentTimeDateRadioButton.Checked = true; + break; + + case TODPowerUpSetMode.HostTimeY2K: + CurrentTimeDateY2KRadioButton.Checked = true; + break; + + case TODPowerUpSetMode.SpecificDateAndTime: + SpecifiedTimeDateRadioButton.Checked = true; + break; + + case TODPowerUpSetMode.SpecificDate: + SpecifiedDateRadioButton.Checked = true; + break; + + case TODPowerUpSetMode.NoChange: + NoTimeDateChangeRadioButton.Checked = true; + break; + } + + TODDateTimePicker.Value = TODDateTime; + TODDatePicker.Value = TODDate; + } + + private void PopulateNetworkAdapterList() + { + // + // Populate the list with the interfaces available on the machine, if any. + // + EthernetInterfaceListBox.Enabled = Configuration.HostRawEthernetInterfacesAvailable; + EthernetInterfaceListBox.Items.Clear(); + + + // Add the "Use no interface" option + EthernetInterfaceListBox.Items.Add( + new EthernetInterface("None", "No network adapter")); + + // Add all interfaces that PCAP knows about. + + if (Configuration.HostRawEthernetInterfacesAvailable) + { + if (Configuration.Platform == PlatformType.Windows) + { + foreach (WinPcapDevice device in CaptureDeviceList.Instance) + { + EthernetInterfaceListBox.Items.Add( + new EthernetInterface(device.Interface.FriendlyName, device.Interface.Description)); + } + } + else + { + foreach (SharpPcap.LibPcap.LibPcapLiveDevice device in CaptureDeviceList.Instance) + { + EthernetInterfaceListBox.Items.Add( + new EthernetInterface(device.Interface.FriendlyName, device.Interface.Description)); + } + } + } + + // + // Select the one that is already selected (if any) + // + EthernetInterfaceListBox.SelectedIndex = 0; + + if (!string.IsNullOrEmpty(Configuration.HostPacketInterfaceName)) + { + for (int i = 0; i < EthernetInterfaceListBox.Items.Count; i++) + { + EthernetInterface iface = (EthernetInterface)EthernetInterfaceListBox.Items[i]; + + if (iface.Name == HostPacketInterfaceName) + { + EthernetInterfaceListBox.SelectedIndex = i; + break; + } + } + } + } + + private void OnLoad(object sender, System.EventArgs e) + { + PopulateUI(); + } + + private void OnMemorySizeChanged(object sender, EventArgs e) + { + MemorySize = (uint)((MemorySizeComboBox.SelectedIndex + 1) * 128); + } + + private void OnHostIDValidating(object sender, System.ComponentModel.CancelEventArgs e) + { + // + // Validate the input -- remove spaces, colons, etc. + // + string stripped = HostIDTextBox.Text.Replace(":", string.Empty); + stripped = stripped.Replace(" ", string.Empty); + + try + { + ulong newValue = Convert.ToUInt64(stripped, 16); + + // Ensure the value is in range + if ((newValue & 0xffff0000000000) != 0) + { + throw new ArgumentOutOfRangeException("HostID"); + } + + HostID = newValue; + } + catch + { + // + // Any failure, we reset back to default. + // + MessageBox.Show("Invalid value for Host ID"); + HostIDTextBox.Text = string.Format("{0:x12}", HostID); + } + } + + private void OnDisplayScaleChanged(object sender, EventArgs e) + { + DisplayScale = (uint)(DisplayScaleComboBox.SelectedIndex + 1); + } + + private void OnClosed(object sender, FormClosedEventArgs e) + { + ThrottleSpeed = ThrottleSpeedCheckBox.Checked; + SlowPhosphor = SlowPhosphorCheckBox.Checked; + + HostPacketInterfaceName = ((EthernetInterface)EthernetInterfaceListBox.SelectedItem).Name; + + if (CurrentTimeDateRadioButton.Checked) + { + TODSetMode = TODPowerUpSetMode.HostTime; + } + else if (CurrentTimeDateY2KRadioButton.Checked) + { + TODSetMode = TODPowerUpSetMode.HostTimeY2K; + } + else if (SpecifiedTimeDateRadioButton.Checked) + { + TODSetMode = TODPowerUpSetMode.SpecificDateAndTime; + TODDateTime = TODDateTimePicker.Value; + } + else if (SpecifiedDateRadioButton.Checked) + { + TODSetMode = TODPowerUpSetMode.SpecificDate; + TODDate = TODDatePicker.Value; + } + else if (NoTimeDateChangeRadioButton.Checked) + { + TODSetMode = TODPowerUpSetMode.NoChange; + } + } + } +} diff --git a/D/UI/ConfigurationDialog.resx b/D/UI/ConfigurationDialog.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/D/UI/ConfigurationDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/D/UI/DWindow-IO.cs b/D/UI/DWindow-IO.cs new file mode 100644 index 0000000..53c2c04 --- /dev/null +++ b/D/UI/DWindow-IO.cs @@ -0,0 +1,1096 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.IOP; +using SDL2; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Runtime.InteropServices; +using System.Threading; +using System.Windows.Forms; + +namespace D.UI +{ + /// + /// This portion of the DWindow class implements methods related to + /// providing the emulator's display and keyboard/mouse inputs. + /// + /// At the moment it uses SDL for display and mouse, but WinForms for keyboard -- WinForms + /// appears to swallow all keyboard events in its message loop whether they're handled or not, + /// so SDL never sees them. + /// It would be preferable to get SDL to handle all of this, and leave WinForms to manage only + /// the user-interface portions. (Really, it would be preferable to use a cross-platform + /// UI library that has decent C# wrappers but I've yet to find one that actually works. + /// WinForms at least nominally works on *nix/OS X platforms under mono though it is quite buggy on + /// OS X.) + /// + public partial class DWindow : Form + { + private void InitializeIO() + { + UpdateDisplayScale(); + UpdateSlowPhosphor(); + + InitializeKeymapSDL(); + InitializeKeymapWinforms(); + + InitializeSDL(); + + _currentCursorState = true; + _mouseCaptured = false; + _skipNextMouseMove = false; + + _capsLock = false; + } + + /// + /// Renders a full scanline of pixels to the current frame. + /// + /// + /// + /// + public void DrawScanline(int scanline, ushort[] scanlineData, bool invert) + { + int rgbIndex = scanline * _displayWidth; + uint onColor; + uint offColor; + if (invert) + { + onColor = _litPixel; + offColor = _offPixel; + } + else + { + offColor = _litPixel; + onColor = _offPixel; + } + + for (int i = 0; i < scanlineData.Length; i++) + { + ushort w = scanlineData[i]; + for (int bit = 15; bit >= 0; bit--) + { + uint color = (w & (1 << bit)) == 0 ? offColor : onColor; + _32bppDisplayBuffer[rgbIndex++] = (int)(color); + } + } + } + + public void Clear() + { + uint blankColor = 0xff000000; + for(int i=0;i<_32bppDisplayBuffer.Length;i++) + { + _32bppDisplayBuffer[i] = (int)blankColor; + } + + Render(); + } + + /// + /// Renders a completed frame to the screen. + /// + public void Render() + { + // + // Send a render event to the SDL message loop so that things + // will get rendered. + // + BeginInvoke(new DisplayDelegate(RenderDisplay)); + + _frameCount++; + } + + protected override void OnLoad(EventArgs e) + { + // + // Kick off our SDL message loop. + // + _sdlThread = new Thread(SDLMessageLoopThread); + _sdlThread.Start(); + + // Clear the display. + Clear(); + + base.OnLoad(e); + } + + private void RenderDisplay() + { + // + // Stuff the display data into the display texture + // + IntPtr textureBits = IntPtr.Zero; + int pitch = 0; + SDL.SDL_LockTexture(_displayTexture, IntPtr.Zero, out textureBits, out pitch); + + Marshal.Copy(_32bppDisplayBuffer, 0, textureBits, _32bppDisplayBuffer.Length); + + SDL.SDL_UnlockTexture(_displayTexture); + + // + // Render the display texture to the renderer + // + SDL.SDL_RenderCopy(_sdlRenderer, _displayTexture, IntPtr.Zero, IntPtr.Zero); + + // + // And show it to us. + // + SDL.SDL_RenderPresent(_sdlRenderer); + } + + private void SDLMessageLoopThread() + { + _sdlRunning = true; + + while (_sdlRunning) + { + SDL.SDL_Event e; + + // + // Run main message loop + // + while (SDL.SDL_WaitEvent(out e) != 0) + { + if (e.type == SDL.SDL_EventType.SDL_QUIT) + { + _sdlRunning = false; + break; + } + else + { + // + // Ensure things get run on the UI thread. + // + Invoke(new SDLMessageHandlerDelegate(SDLMessageHandler), e); + } + } + + SDL.SDL_Delay(0); + } + + // + // Shut things down nicely. + // + if (_sdlRenderer != IntPtr.Zero) + { + SDL.SDL_DestroyRenderer(_sdlRenderer); + _sdlRenderer = IntPtr.Zero; + } + + // + // We only destroy the window and call SDL_Quit() on Windows systems + // when cleaning up. + // Doing so on *nix results in SDL_DestroyWindow and SDL_Quit() hanging forever. + // At least on my Debian box. Why? NO IDEA. Platform independence + // is a a nightmare I shall never awaken from. + // + if (Configuration.Platform == PlatformType.Windows) + { + if (_sdlWindow != IntPtr.Zero) + { + SDL.SDL_DestroyWindow(_sdlWindow); + _sdlWindow = IntPtr.Zero; + } + + SDL.SDL_Quit(); + } + } + + private void SDLMessageHandler(SDL.SDL_Event e) + { + // + // Handle current messages. This executes in the UI context. + // + switch (e.type) + { + case SDL.SDL_EventType.SDL_USEREVENT: + // This should always be the case since we only define one + // user event, but just to be truly pedantic... + if (e.user.type == _renderEventType) + { + RenderDisplay(); + } + break; + + /* + case SDL.SDL_EventType.SDL_MOUSEMOTION: + DoMouseMove(e.motion.x, e.motion.y); + break; + + case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN: + DoMouseDown(e.button.button, e.button.x, e.button.y); + break; + + case SDL.SDL_EventType.SDL_MOUSEBUTTONUP: + DoMouseUp(e.button.button); + break; */ + + /* + * These do not currently function due to WinForms's + * message loop getting in the way. + * + case SDL.SDL_EventType.SDL_KEYDOWN: + SdlKeyDown(e.key.keysym.sym); + break; + + case SDL.SDL_EventType.SDL_KEYUP: + SdlKeyUp(e.key.keysym.sym); + break; + */ + default: + break; + } + + } + + private void SdlKeyDown(SDL.SDL_Keycode key) + { + // + // Check for Alt key to release mouse + // + if (key == SDL.SDL_Keycode.SDLK_LALT || + key == SDL.SDL_Keycode.SDLK_RALT) + { + ReleaseMouse(); + } + + if (!_mouseCaptured) + { + return; + } + + if (_sdlKeyMap.ContainsKey(key)) + { + KeyCode code = _sdlKeyMap[key]; + + if (code == KeyCode.Lock) + { + if (!_capsLock) + { + // Latch Lock + _system.IOP.Keyboard.KeyDown(code); + } + else + { + // Release + _system.IOP.Keyboard.KeyUp(code); + } + + _capsLock = !_capsLock; + } + else + { + _system.IOP.Keyboard.KeyDown(code); + } + } + } + + private void SdlKeyUp(SDL.SDL_Keycode key) + { + if (!_mouseCaptured) + { + return; + } + + if (_sdlKeyMap.ContainsKey(key)) + { + KeyCode code = _sdlKeyMap[key]; + + if (code != KeyCode.Lock) + { + _system.IOP.Keyboard.KeyUp(code); + } + } + } + + /// + /// Handle modifier keys here mostly because Windows Forms doesn't + /// normally distinguish between left and right Shift or left and + /// right Control keys. + /// + /// + /// + protected override bool ProcessKeyEventArgs(ref Message m) + { + bool extended = (m.LParam.ToInt64() & 0x1000000) != 0; + bool down = false; + + const int WM_KEYDOWN = 0x100; + const int WM_KEYUP = 0x101; + + + if (m.Msg == WM_KEYDOWN) + { + down = true; + } + else if (m.Msg == WM_KEYUP) + { + down = false; + } + else + { + // Something else? + return base.ProcessKeyEventArgs(ref m); + } + + KeyCode modifierKey = KeyCode.Invalid; + + if (Configuration.Platform == PlatformType.Windows) + { + switch (m.WParam.ToInt64()) + { + case 0x10: + // Shift + modifierKey = extended ? KeyCode.RightShift : KeyCode.LeftShift; + break; + + case 0x11: + // Control + modifierKey = extended ? KeyCode.Open : KeyCode.Properties; + break; + } + } + else + { + // + // This is a workaround for an apparent bug in Mono's Winforms implementation: + // The extended bit for RShift and RControl isn't set properly -- for Control + // it inexplicably gets set on WM_KEYUP, for Shift it never gets set at all. + // Instead we look at the scancode coming in the lparam -- this is implementation + // dependent so it probably isn't a great idea and may be fragile. + // + switch ((m.LParam.ToInt64() >> 16) & 0xff) + { + case 0x25: + modifierKey = KeyCode.Properties; + break; + + case 0x69: + modifierKey = KeyCode.Open; + break; + + case 0x32: + modifierKey = KeyCode.LeftShift; + break; + + case 0x3e: + modifierKey = KeyCode.RightShift; + break; + } + } + + if (modifierKey != KeyCode.Invalid) + { + if (down) + { + _system.IOP.Keyboard.KeyDown(modifierKey); + } + else + { + _system.IOP.Keyboard.KeyUp(modifierKey); + } + + return true; // handled + } + + return base.ProcessKeyEventArgs(ref m); + } + + private void OnKeyDown(object sender, KeyEventArgs e) + { + // Handle non-modifier keys here + if (_winKeyMap.ContainsKey(e.KeyCode)) + { + // + // Handle the "Lock" (Caps Lock) key specially -- the real keyboard + // has a latch that holds it down the first time it is pressed, and + // releases it the second time. We simulate that behavior here. + // + KeyCode code = _winKeyMap[e.KeyCode]; + + if (code == KeyCode.Lock) + { + if (!_capsLock) + { + // Latch Lock + _system.IOP.Keyboard.KeyDown(code); + } + else + { + // Release + _system.IOP.Keyboard.KeyUp(code); + } + + _capsLock = !_capsLock; + } + else + { + _system.IOP.Keyboard.KeyDown(code); + } + } + + if (e.Alt) + { + ReleaseMouse(); + e.SuppressKeyPress = true; + } + } + + private void OnKeyUp(object sender, KeyEventArgs e) + { + // Handle non-modifier keys here + if (_winKeyMap.ContainsKey(e.KeyCode)) + { + KeyCode code = _winKeyMap[e.KeyCode]; + + if (code != KeyCode.Lock) + { + _system.IOP.Keyboard.KeyUp(code); + } + } + + if (e.Alt) + { + ReleaseMouse(); + e.SuppressKeyPress = true; + } + } + + private void OnWinformsMouseMove(object sender, MouseEventArgs e) + { + if (!_mouseCaptured) + { + return; + } + + if (_skipNextMouseMove) + { + _skipNextMouseMove = false; + return; + } + + // + // Calclate the center of the window. + // + Point middle = new Point(DisplayBox.Width / 2, DisplayBox.Height / 2); + + int dx = e.X - middle.X; + int dy = e.Y - middle.Y; + + if (dx != 0 || dy != 0) + { + _system.IOP.Mouse.MouseMove(dx, dy); + + // Don't handle the very next Mouse Move event (which will just be the motion we caused in the + // below line...) + _skipNextMouseMove = true; + + Cursor.Position = DisplayBox.PointToScreen(middle); + } + } + + private void DoMouseMove(int x, int y) + { + if (!_mouseCaptured) + { + return; + } + + if (_skipNextMouseMove) + { + _skipNextMouseMove = false; + return; + } + + // + // Calclate the center of the window. + // + int mx = DisplayBox.Width / 2; + int my = DisplayBox.Height / 2; + + int dx = x - mx; + int dy = y - my; + + if (dx != 0 || dy != 0) + { + _system.IOP.Mouse.MouseMove(dx, dy); + + // Don't handle the very next Mouse Move event (which will just be the motion we caused in the + // below line...) + _skipNextMouseMove = true; + + // + // Move the mouse pointer to the middle of the window. + // + SDL.SDL_WarpMouseInWindow(_sdlWindow, mx, my); + } + } + + private void OnWinformsMouseDown(object sender, MouseEventArgs e) + { + if (!_mouseCaptured) + { + return; + } + + StarMouseButton starButton = GetMouseButtonWinforms(e.Button); + + if (starButton != StarMouseButton.None) + { + _system.IOP.Mouse.MouseDown(starButton); + } + } + + private void DoMouseDown(byte button, int x, int y) + { + // + // OS X Sierra issue: we get mousedown events when the window title + // is clicked, sometimes. These always show up with a Y coordinate + // of zero. So ignore those only for mouse-capture purposes as + // a workaround. + // + if (!_mouseCaptured && (x <= 0 || y <= 0)) + { + return; + } + + if (!_mouseCaptured) + { + + return; + } + + StarMouseButton starButton = GetMouseButtonSDL(button); + + if (starButton != StarMouseButton.None) + { + _system.IOP.Mouse.MouseDown(starButton); + } + } + + private void OnWinformsMouseUp(object sender, MouseEventArgs e) + { + if (!_mouseCaptured) + { + CaptureMouse(); + return; + } + + StarMouseButton starButton = GetMouseButtonWinforms(e.Button); + + if (starButton != StarMouseButton.None) + { + _system.IOP.Mouse.MouseUp(starButton); + } + } + + private void DoMouseUp(byte button) + { + if (!_mouseCaptured) + { + CaptureMouse(); + return; + } + + StarMouseButton starButton = GetMouseButtonSDL(button); + + if (starButton != StarMouseButton.None) + { + _system.IOP.Mouse.MouseUp(starButton); + } + } + + private void CaptureMouse() + { + // + // Turn off the mouse cursor (both for SDL and WinForms) and ensure the mouse is trapped + // within our window. + // + if (_system.IsExecuting) + { + _mouseCaptured = true; + SDL.SDL_ShowCursor(0); + ShowCursor(false); + SDL.SDL_SetWindowGrab(_sdlWindow, SDL.SDL_bool.SDL_TRUE); + } + + UpdateMouseState(); + } + + private void ReleaseMouse() + { + // + // Turn the mouse cursor back on (both for SDL and WinForms), and release the mouse. + // + _mouseCaptured = false; + SDL.SDL_ShowCursor(1); + ShowCursor(true); + SDL.SDL_SetWindowGrab(_sdlWindow, SDL.SDL_bool.SDL_FALSE); + + UpdateMouseState(); + } + + + private void OnWindowLeave(object sender, EventArgs e) + { + // We are no longer the focus, make sure to release the mouse. + ReleaseMouse(); + } + + private void OnWindowDeactivate(object sender, EventArgs e) + { + // We are no longer the focus, make sure to release the mouse. + ReleaseMouse(); + } + + /// + /// This works around Winforms's ref-counted cursor behavior. + /// + /// + private void ShowCursor(bool show) + { + if (show == _currentCursorState) + { + return; + } + + if (show) + { + Cursor.Show(); + } + else + { + Cursor.Hide(); + } + + _currentCursorState = show; + } + + private void UpdateDisplayScale() + { + _displayScale = Configuration.DisplayScale; + // + // Force the UIPanel (which holds the Star's display plus the status bar) + // to be the dimensions of the (possibly scaled) display + status bar. + // This will cause the window to resize to fit. + // + UIPanel.Size = + new Size( + (int)(_displayWidth * _displayScale), + (int)(_displayHeight * _displayScale) + this.SystemStatus.Height); + } + + private void UpdateSlowPhosphor() + { + if (Configuration.SlowPhosphor) + { + _litPixel = _litPixelSlow; + _offPixel = _offPixelSlow; + } + else + { + _litPixel = _litPixelNormal; + _offPixel = _offPixelNormal; + } + } + + private void InitializeKeymapWinforms() + { + _winKeyMap = new Dictionary(); + + _winKeyMap.Add(Keys.A, KeyCode.A); + _winKeyMap.Add(Keys.B, KeyCode.B); + _winKeyMap.Add(Keys.C, KeyCode.C); + _winKeyMap.Add(Keys.D, KeyCode.D); + _winKeyMap.Add(Keys.E, KeyCode.E); + _winKeyMap.Add(Keys.F, KeyCode.F); + _winKeyMap.Add(Keys.G, KeyCode.G); + _winKeyMap.Add(Keys.H, KeyCode.H); + _winKeyMap.Add(Keys.I, KeyCode.I); + _winKeyMap.Add(Keys.J, KeyCode.J); + _winKeyMap.Add(Keys.K, KeyCode.K); + _winKeyMap.Add(Keys.L, KeyCode.L); + _winKeyMap.Add(Keys.M, KeyCode.M); + _winKeyMap.Add(Keys.N, KeyCode.N); + _winKeyMap.Add(Keys.O, KeyCode.O); + _winKeyMap.Add(Keys.P, KeyCode.P); + _winKeyMap.Add(Keys.Q, KeyCode.Q); + _winKeyMap.Add(Keys.R, KeyCode.R); + _winKeyMap.Add(Keys.S, KeyCode.S); + _winKeyMap.Add(Keys.T, KeyCode.T); + _winKeyMap.Add(Keys.U, KeyCode.U); + _winKeyMap.Add(Keys.V, KeyCode.V); + _winKeyMap.Add(Keys.W, KeyCode.W); + _winKeyMap.Add(Keys.X, KeyCode.X); + _winKeyMap.Add(Keys.Y, KeyCode.Y); + _winKeyMap.Add(Keys.Z, KeyCode.Z); + + _winKeyMap.Add(Keys.D0, KeyCode.N0); + _winKeyMap.Add(Keys.D1, KeyCode.N1); + _winKeyMap.Add(Keys.D2, KeyCode.N2); + _winKeyMap.Add(Keys.D3, KeyCode.N3); + _winKeyMap.Add(Keys.D4, KeyCode.N4); + _winKeyMap.Add(Keys.D5, KeyCode.N5); + _winKeyMap.Add(Keys.D6, KeyCode.N6); + _winKeyMap.Add(Keys.D7, KeyCode.N7); + _winKeyMap.Add(Keys.D8, KeyCode.N8); + _winKeyMap.Add(Keys.D9, KeyCode.N9); + + _winKeyMap.Add(Keys.OemMinus, KeyCode.Minus); + _winKeyMap.Add(Keys.Oemplus, KeyCode.Equals); + + _winKeyMap.Add(Keys.Return, KeyCode.Return); + _winKeyMap.Add(Keys.Space, KeyCode.Space); + _winKeyMap.Add(Keys.Back, KeyCode.Backspace); + _winKeyMap.Add(Keys.OemOpenBrackets, KeyCode.LBracket); + _winKeyMap.Add(Keys.OemCloseBrackets, KeyCode.RBracket); + _winKeyMap.Add(Keys.OemPeriod, KeyCode.Period); + _winKeyMap.Add(Keys.Oemcomma, KeyCode.Comma); + _winKeyMap.Add(Keys.OemQuestion, KeyCode.FSlash); + _winKeyMap.Add(Keys.OemSemicolon, KeyCode.Colon); + _winKeyMap.Add(Keys.OemQuotes, KeyCode.Quote); + _winKeyMap.Add(Keys.Oemtilde, KeyCode.BackQuote); + _winKeyMap.Add(Keys.Right, KeyCode.FArrow); + _winKeyMap.Add(Keys.Tab, KeyCode.Tab); + _winKeyMap.Add(Keys.CapsLock, KeyCode.Lock); + + // Left key block (Open/Properties are also mapped to the Ctrl keys + // as they are used as Meta/Ctrl in interlisp) + _winKeyMap.Add(Keys.F1, KeyCode.Again); + _winKeyMap.Add(Keys.F2, KeyCode.Delete); + _winKeyMap.Add(Keys.F3, KeyCode.Find); + _winKeyMap.Add(Keys.F4, KeyCode.Copy); + _winKeyMap.Add(Keys.F5, KeyCode.Same); + _winKeyMap.Add(Keys.F6, KeyCode.Move); + _winKeyMap.Add(Keys.F7, KeyCode.Open); + _winKeyMap.Add(Keys.F8, KeyCode.Properties); + + // Top key block + _winKeyMap.Add(Keys.F9, KeyCode.Center); + _winKeyMap.Add(Keys.F10, KeyCode.Bold); + _winKeyMap.Add(Keys.F11, KeyCode.Italics); + _winKeyMap.Add(Keys.F12, KeyCode.Underline); + _winKeyMap.Add(Keys.PrintScreen, KeyCode.Superscript); + _winKeyMap.Add(Keys.Scroll, KeyCode.Subscript); + _winKeyMap.Add(Keys.Pause, KeyCode.LargerSmaller); + _winKeyMap.Add(Keys.NumLock, KeyCode.Defaults); + + // Right key block + _winKeyMap.Add(Keys.Home, KeyCode.SkipNext); + _winKeyMap.Add(Keys.PageUp, KeyCode.Undo); + _winKeyMap.Add(Keys.End, KeyCode.DefnExpand); + _winKeyMap.Add(Keys.PageDown, KeyCode.Stop); + _winKeyMap.Add(Keys.Up, KeyCode.Help); + _winKeyMap.Add(Keys.Left, KeyCode.Margins); + _winKeyMap.Add(Keys.OemPipe, KeyCode.Font); // Mapped to backslash as interlisp uses Font as \ key. + _winKeyMap.Add(Keys.Down, KeyCode.Keyboard); + } + + private void InitializeKeymapSDL() + { + _sdlKeyMap = new Dictionary(); + + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_a, KeyCode.A); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_b, KeyCode.B); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_c, KeyCode.C); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_d, KeyCode.D); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_e, KeyCode.E); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_f, KeyCode.F); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_g, KeyCode.G); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_h, KeyCode.H); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_i, KeyCode.I); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_j, KeyCode.J); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_k, KeyCode.K); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_l, KeyCode.L); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_m, KeyCode.M); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_n, KeyCode.N); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_o, KeyCode.O); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_p, KeyCode.P); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_q, KeyCode.Q); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_r, KeyCode.R); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_s, KeyCode.S); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_t, KeyCode.T); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_u, KeyCode.U); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_v, KeyCode.V); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_w, KeyCode.W); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_x, KeyCode.X); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_y, KeyCode.Y); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_z, KeyCode.Z); + + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_0, KeyCode.N0); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_1, KeyCode.N1); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_2, KeyCode.N2); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_3, KeyCode.N3); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_4, KeyCode.N4); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_5, KeyCode.N5); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_6, KeyCode.N6); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_7, KeyCode.N7); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_8, KeyCode.N8); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_9, KeyCode.N9); + + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_EQUALS, KeyCode.Equals); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_MINUS, KeyCode.Minus); + + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_RETURN, KeyCode.Return); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_SPACE, KeyCode.Space); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_BACKSPACE, KeyCode.Backspace); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_LEFTBRACKET, KeyCode.LBracket); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_RIGHTBRACKET, KeyCode.RBracket); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_PERIOD, KeyCode.Period); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_COMMA, KeyCode.Comma); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_SLASH, KeyCode.FSlash); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_SEMICOLON, KeyCode.Colon); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_QUOTE, KeyCode.Quote); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_TAB, KeyCode.Tab); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_RIGHT, KeyCode.FArrow); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_BACKSLASH, KeyCode.Font); // Mapped to backslash as interlisp uses Font as \ key. + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_CAPSLOCK, KeyCode.Lock); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_BACKQUOTE, KeyCode.BackQuote); + + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_LSHIFT, KeyCode.LeftShift); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_RSHIFT, KeyCode.RightShift); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_LCTRL, KeyCode.Open); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_RCTRL, KeyCode.Properties); + + // Left key block (Open/Properties are also mapped to the Ctrl keys + // as they are used as Meta/Ctrl in interlisp) + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F1, KeyCode.Again); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F2, KeyCode.Delete); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F3, KeyCode.Find); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F4, KeyCode.Copy); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F5, KeyCode.Same); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F6, KeyCode.Move); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F7, KeyCode.Open); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F8, KeyCode.Properties); + + // Top key block + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F9, KeyCode.Center); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F10, KeyCode.Bold); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F11, KeyCode.Italics); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_F12, KeyCode.Underline); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_PRINTSCREEN, KeyCode.Superscript); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_SCROLLLOCK, KeyCode.Subscript); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_PAUSE, KeyCode.LargerSmaller); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_NUMLOCKCLEAR, KeyCode.Defaults); + + // Right key block + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_HOME, KeyCode.SkipNext); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_PAGEUP, KeyCode.Undo); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_END, KeyCode.DefnExpand); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_PAGEDOWN, KeyCode.Stop); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_UP, KeyCode.Help); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_LEFT, KeyCode.Margins); + _sdlKeyMap.Add(SDL.SDL_Keycode.SDLK_DOWN, KeyCode.Keyboard); + } + + /// + /// Maps the SDL mouse button to the Star's mouse button. + /// + /// + /// + private static StarMouseButton GetMouseButtonSDL(byte button) + { + StarMouseButton starButton = StarMouseButton.None; + + switch (button) + { + case 1: + starButton = StarMouseButton.Left; + break; + + case 2: + starButton = StarMouseButton.Middle; + break; + + case 3: + starButton = StarMouseButton.Right; + break; + } + + return starButton; + } + + /// + /// Maps the Winforms mouse button to the Star's mouse button. + /// + /// + /// + private static StarMouseButton GetMouseButtonWinforms(MouseButtons button) + { + StarMouseButton starButton = StarMouseButton.None; + + switch (button) + { + case MouseButtons.Left: + starButton = StarMouseButton.Left; + break; + + case MouseButtons.Middle: + starButton = StarMouseButton.Middle; + break; + + case MouseButtons.Right: + starButton = StarMouseButton.Right; + break; + } + + return starButton; + } + + private void InitializeSDL() + { + int retVal; + + // Get SDL humming + if ((retVal = SDL.SDL_Init(SDL.SDL_INIT_VIDEO)) < 0) + { + throw new InvalidOperationException(String.Format("SDL_Init failed. Error {0:x}", retVal)); + } + + // + if (SDL.SDL_SetHint(SDL.SDL_HINT_RENDER_SCALE_QUALITY, "0") == SDL.SDL_bool.SDL_FALSE) + { + throw new InvalidOperationException("SDL_SetHint failed to set scale quality."); + } + + _sdlWindow = SDL.SDL_CreateWindowFrom(DisplayBox.Handle); + + if (_sdlWindow == IntPtr.Zero) + { + throw new InvalidOperationException("SDL_CreateWindow failed."); + } + + if (Configuration.Platform == PlatformType.Unix) + { + // On UNIX platforms it appears that asking for hardware acceleration causes SDL_CreateRenderer + // to hang indefinitely. For the time being, we'll default to software rendering. + _sdlRenderer = SDL.SDL_CreateRenderer(_sdlWindow, -1, SDL.SDL_RendererFlags.SDL_RENDERER_SOFTWARE); + + if (_sdlRenderer == IntPtr.Zero) + { + // No luck. + throw new InvalidOperationException("SDL_CreateRenderer failed."); + } + } + else + { + _sdlRenderer = SDL.SDL_CreateRenderer(_sdlWindow, -1, SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED); + if (_sdlRenderer == IntPtr.Zero) + { + // Fall back to software + _sdlRenderer = SDL.SDL_CreateRenderer(_sdlWindow, -1, SDL.SDL_RendererFlags.SDL_RENDERER_SOFTWARE); + + if (_sdlRenderer == IntPtr.Zero) + { + // Still no luck. + throw new InvalidOperationException("SDL_CreateRenderer failed."); + } + } + } + + _displayTexture = SDL.SDL_CreateTexture( + _sdlRenderer, + SDL.SDL_PIXELFORMAT_ARGB8888, + (int)SDL.SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, + _displayWidth, + _displayHeight); + + if (_displayTexture == IntPtr.Zero) + { + throw new InvalidOperationException("SDL_CreateTexture failed."); + } + + SDL.SDL_SetTextureBlendMode(_displayTexture, SDL.SDL_BlendMode.SDL_BLENDMODE_BLEND); + SDL.SDL_SetRenderDrawColor(_sdlRenderer, 0x00, 0x00, 0x00, 0xff); + + // Register a User event for rendering. + _renderEventType = SDL.SDL_RegisterEvents(1); + _renderEvent = new SDL.SDL_Event(); + _renderEvent.type = (SDL.SDL_EventType)_renderEventType; + } + + private DSystem _system; + + // + // Display data + // + // + // Buffer for rendering pixels. SDL doesn't support 1bpp pixel formats, so to keep things simple we use + // an array of ints and a 32bpp format. What's a few extra bits between friends. + // + private int[] _32bppDisplayBuffer = new int[(_displayWidth * _displayHeight)]; + private uint _litPixel; + private uint _offPixel; + private delegate void DisplayDelegate(); + private delegate void SDLMessageHandlerDelegate(SDL.SDL_Event e); + + private const uint _litPixelSlow = 0xffeffeff; // slightly bluish + private const uint _offPixelSlow = 0x20000000; // provides a fakey-phosphor persistence + + private const uint _litPixelNormal = 0xffeffeff; // slightly bluish + private const uint _offPixelNormal = 0xff000000; + + private const int _displayWidth = 1088; + private const int _displayHeight = 860; + private double _displayScale; + private int _frameCount; + + + // + // Keyboard data + // + private Dictionary _winKeyMap; + private Dictionary _sdlKeyMap; + private bool _capsLock; + + // + // Mouse data + // + private bool _skipNextMouseMove; + private bool _mouseCaptured; + private bool _currentCursorState; + + // + // SDL + // + private IntPtr _sdlWindow = IntPtr.Zero; + private IntPtr _sdlRenderer = IntPtr.Zero; + + private UInt32 _renderEventType; + private SDL.SDL_Event _renderEvent; + + private Thread _sdlThread; + private bool _sdlRunning; + + // Rendering textures + private IntPtr _displayTexture = IntPtr.Zero; + } +} diff --git a/D/UI/DWindow.Designer.cs b/D/UI/DWindow.Designer.cs new file mode 100644 index 0000000..f2560f8 --- /dev/null +++ b/D/UI/DWindow.Designer.cs @@ -0,0 +1,409 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +namespace D.UI +{ + partial class DWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DWindow)); + this.SystemMenu = new System.Windows.Forms.MenuStrip(); + this.SystemToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.StartToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ResetToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.AlternateBootToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.floppyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.FloppyLoadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.FloppyUnloadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.FloppyLabelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.HardDiskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.LoadHardDiskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.NewHardDiskToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.NewSA1004ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.NewQ2040ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.NewQ2080ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.HardDiskLabelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ConfigurationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.showDebuggerToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ExitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.HelpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.ViewDocumentationToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.UIPanel = new System.Windows.Forms.Panel(); + this.DisplayBox = new System.Windows.Forms.Panel(); + this.SystemStatus = new System.Windows.Forms.StatusStrip(); + this.MPStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.ExecutionStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.FPSStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.MouseCaptureStatusLabel = new System.Windows.Forms.ToolStripStatusLabel(); + this.ProgressBar = new System.Windows.Forms.ToolStripProgressBar(); + this.SystemMenu.SuspendLayout(); + this.UIPanel.SuspendLayout(); + this.SystemStatus.SuspendLayout(); + this.SuspendLayout(); + // + // SystemMenu + // + this.SystemMenu.ImageScalingSize = new System.Drawing.Size(24, 24); + this.SystemMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.SystemToolStripMenuItem, + this.HelpToolStripMenuItem}); + this.SystemMenu.Location = new System.Drawing.Point(0, 0); + this.SystemMenu.Name = "SystemMenu"; + this.SystemMenu.Size = new System.Drawing.Size(1113, 24); + this.SystemMenu.TabIndex = 1; + this.SystemMenu.Text = "System Menu"; + // + // SystemToolStripMenuItem + // + this.SystemToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.StartToolStripMenuItem, + this.ResetToolStripMenuItem, + this.AlternateBootToolStripMenuItem, + this.floppyToolStripMenuItem, + this.HardDiskToolStripMenuItem, + this.ConfigurationToolStripMenuItem, + this.showDebuggerToolStripMenuItem, + this.ExitToolStripMenuItem}); + this.SystemToolStripMenuItem.Name = "SystemToolStripMenuItem"; + this.SystemToolStripMenuItem.Size = new System.Drawing.Size(57, 20); + this.SystemToolStripMenuItem.Text = "System"; + // + // StartToolStripMenuItem + // + this.StartToolStripMenuItem.Name = "StartToolStripMenuItem"; + this.StartToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.StartToolStripMenuItem.Text = "Start"; + this.StartToolStripMenuItem.Click += new System.EventHandler(this.StartToolStripMenuItem_Click); + // + // ResetToolStripMenuItem + // + this.ResetToolStripMenuItem.Name = "ResetToolStripMenuItem"; + this.ResetToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.ResetToolStripMenuItem.Text = "Reset"; + this.ResetToolStripMenuItem.Click += new System.EventHandler(this.ResetToolStripMenuItem_Click); + // + // AlternateBootToolStripMenuItem + // + this.AlternateBootToolStripMenuItem.Name = "AlternateBootToolStripMenuItem"; + this.AlternateBootToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.AlternateBootToolStripMenuItem.Text = "Alternate Boot"; + // + // floppyToolStripMenuItem + // + this.floppyToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.FloppyLoadToolStripMenuItem, + this.FloppyUnloadToolStripMenuItem, + this.FloppyLabelToolStripMenuItem}); + this.floppyToolStripMenuItem.Name = "floppyToolStripMenuItem"; + this.floppyToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.floppyToolStripMenuItem.Text = "Floppy Disk"; + // + // FloppyLoadToolStripMenuItem + // + this.FloppyLoadToolStripMenuItem.Name = "FloppyLoadToolStripMenuItem"; + this.FloppyLoadToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + this.FloppyLoadToolStripMenuItem.Text = "Load..."; + this.FloppyLoadToolStripMenuItem.Click += new System.EventHandler(this.FloppyLoadToolStripMenuItem_Click); + // + // FloppyUnloadToolStripMenuItem + // + this.FloppyUnloadToolStripMenuItem.Name = "FloppyUnloadToolStripMenuItem"; + this.FloppyUnloadToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + this.FloppyUnloadToolStripMenuItem.Text = "Unload"; + this.FloppyUnloadToolStripMenuItem.Click += new System.EventHandler(this.FloppyUnloadToolStripMenuItem_Click); + // + // FloppyLabelToolStripMenuItem + // + this.FloppyLabelToolStripMenuItem.Enabled = false; + this.FloppyLabelToolStripMenuItem.Name = "FloppyLabelToolStripMenuItem"; + this.FloppyLabelToolStripMenuItem.Size = new System.Drawing.Size(171, 22); + this.FloppyLabelToolStripMenuItem.Text = "No Floppy Loaded"; + // + // HardDiskToolStripMenuItem + // + this.HardDiskToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.LoadHardDiskToolStripMenuItem, + this.NewHardDiskToolStripMenuItem, + this.HardDiskLabelToolStripMenuItem}); + this.HardDiskToolStripMenuItem.Name = "HardDiskToolStripMenuItem"; + this.HardDiskToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.HardDiskToolStripMenuItem.Text = "Hard Disk"; + // + // LoadHardDiskToolStripMenuItem + // + this.LoadHardDiskToolStripMenuItem.Name = "LoadHardDiskToolStripMenuItem"; + this.LoadHardDiskToolStripMenuItem.Size = new System.Drawing.Size(191, 22); + this.LoadHardDiskToolStripMenuItem.Text = "Load..."; + this.LoadHardDiskToolStripMenuItem.Click += new System.EventHandler(this.LoadHardDiskToolStripMenuItem_Click); + // + // NewHardDiskToolStripMenuItem + // + this.NewHardDiskToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.NewSA1004ToolStripMenuItem, + this.NewQ2040ToolStripMenuItem, + this.NewQ2080ToolStripMenuItem}); + this.NewHardDiskToolStripMenuItem.Name = "NewHardDiskToolStripMenuItem"; + this.NewHardDiskToolStripMenuItem.Size = new System.Drawing.Size(191, 22); + this.NewHardDiskToolStripMenuItem.Text = "New"; + // + // NewSA1004ToolStripMenuItem + // + this.NewSA1004ToolStripMenuItem.Name = "NewSA1004ToolStripMenuItem"; + this.NewSA1004ToolStripMenuItem.Size = new System.Drawing.Size(153, 22); + this.NewSA1004ToolStripMenuItem.Tag = 1; + this.NewSA1004ToolStripMenuItem.Text = "10MB (SA1004)"; + this.NewSA1004ToolStripMenuItem.Click += new System.EventHandler(this.NewHardDiskToolStripMenuItem_Click); + // + // NewQ2040ToolStripMenuItem + // + this.NewQ2040ToolStripMenuItem.Name = "NewQ2040ToolStripMenuItem"; + this.NewQ2040ToolStripMenuItem.Size = new System.Drawing.Size(153, 22); + this.NewQ2040ToolStripMenuItem.Tag = 2; + this.NewQ2040ToolStripMenuItem.Text = "40MB (Q2040)"; + this.NewQ2040ToolStripMenuItem.Click += new System.EventHandler(this.NewHardDiskToolStripMenuItem_Click); + // + // NewQ2080ToolStripMenuItem + // + this.NewQ2080ToolStripMenuItem.Name = "NewQ2080ToolStripMenuItem"; + this.NewQ2080ToolStripMenuItem.Size = new System.Drawing.Size(153, 22); + this.NewQ2080ToolStripMenuItem.Tag = 3; + this.NewQ2080ToolStripMenuItem.Text = "80MB (Q2080)"; + this.NewQ2080ToolStripMenuItem.Click += new System.EventHandler(this.NewHardDiskToolStripMenuItem_Click); + // + // HardDiskLabelToolStripMenuItem + // + this.HardDiskLabelToolStripMenuItem.Enabled = false; + this.HardDiskLabelToolStripMenuItem.Name = "HardDiskLabelToolStripMenuItem"; + this.HardDiskLabelToolStripMenuItem.Size = new System.Drawing.Size(191, 22); + this.HardDiskLabelToolStripMenuItem.Text = "No Hard Drive Loaded"; + // + // ConfigurationToolStripMenuItem + // + this.ConfigurationToolStripMenuItem.Name = "ConfigurationToolStripMenuItem"; + this.ConfigurationToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.ConfigurationToolStripMenuItem.Text = "Configuration..."; + this.ConfigurationToolStripMenuItem.Click += new System.EventHandler(this.ConfigurationToolStripMenuItem_Click); + // + // showDebuggerToolStripMenuItem + // + this.showDebuggerToolStripMenuItem.Name = "showDebuggerToolStripMenuItem"; + this.showDebuggerToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.showDebuggerToolStripMenuItem.Text = "Show Debugger"; + this.showDebuggerToolStripMenuItem.Click += new System.EventHandler(this.ShowDebuggerToolStripMenu_Click); + // + // ExitToolStripMenuItem + // + this.ExitToolStripMenuItem.Name = "ExitToolStripMenuItem"; + this.ExitToolStripMenuItem.Size = new System.Drawing.Size(158, 22); + this.ExitToolStripMenuItem.Text = "Exit"; + this.ExitToolStripMenuItem.Click += new System.EventHandler(this.ExitToolStripMenuItem_Click); + // + // HelpToolStripMenuItem + // + this.HelpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.ViewDocumentationToolStripMenuItem, + this.aboutToolStripMenuItem}); + this.HelpToolStripMenuItem.Name = "HelpToolStripMenuItem"; + this.HelpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); + this.HelpToolStripMenuItem.Text = "Help"; + // + // ViewDocumentationToolStripMenuItem + // + this.ViewDocumentationToolStripMenuItem.Name = "ViewDocumentationToolStripMenuItem"; + this.ViewDocumentationToolStripMenuItem.Size = new System.Drawing.Size(194, 22); + this.ViewDocumentationToolStripMenuItem.Text = "View Documentation..."; + this.ViewDocumentationToolStripMenuItem.Click += new System.EventHandler(this.ViewDocumentationToolStripMenu_Click); + // + // aboutToolStripMenuItem + // + this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + this.aboutToolStripMenuItem.Size = new System.Drawing.Size(194, 22); + this.aboutToolStripMenuItem.Text = "About..."; + this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); + // + // UIPanel + // + this.UIPanel.AutoSize = true; + this.UIPanel.Controls.Add(this.DisplayBox); + this.UIPanel.Controls.Add(this.SystemStatus); + this.UIPanel.Location = new System.Drawing.Point(0, 27); + this.UIPanel.Name = "UIPanel"; + this.UIPanel.Size = new System.Drawing.Size(711, 323); + this.UIPanel.TabIndex = 2; + // + // DisplayBox + // + this.DisplayBox.Dock = System.Windows.Forms.DockStyle.Fill; + this.DisplayBox.Location = new System.Drawing.Point(0, 0); + this.DisplayBox.Name = "DisplayBox"; + this.DisplayBox.Size = new System.Drawing.Size(711, 299); + this.DisplayBox.TabIndex = 3; + this.DisplayBox.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnWinformsMouseDown); + this.DisplayBox.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnWinformsMouseMove); + this.DisplayBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnWinformsMouseUp); + // + // SystemStatus + // + this.SystemStatus.ImageScalingSize = new System.Drawing.Size(24, 24); + this.SystemStatus.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.MPStatusLabel, + this.ExecutionStatusLabel, + this.FPSStatusLabel, + this.MouseCaptureStatusLabel, + this.ProgressBar}); + this.SystemStatus.Location = new System.Drawing.Point(0, 299); + this.SystemStatus.Name = "SystemStatus"; + this.SystemStatus.Size = new System.Drawing.Size(711, 24); + this.SystemStatus.SizingGrip = false; + this.SystemStatus.TabIndex = 2; + this.SystemStatus.Text = "System Status"; + // + // MPStatusLabel + // + this.MPStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.MPStatusLabel.Name = "MPStatusLabel"; + this.MPStatusLabel.Size = new System.Drawing.Size(94, 19); + this.MPStatusLabel.Text = "MP Placeholder"; + // + // ExecutionStatusLabel + // + this.ExecutionStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.ExecutionStatusLabel.Name = "ExecutionStatusLabel"; + this.ExecutionStatusLabel.Size = new System.Drawing.Size(108, 19); + this.ExecutionStatusLabel.Text = "Status Placeholder"; + // + // FPSStatusLabel + // + this.FPSStatusLabel.BorderSides = System.Windows.Forms.ToolStripStatusLabelBorderSides.Right; + this.FPSStatusLabel.Name = "FPSStatusLabel"; + this.FPSStatusLabel.Size = new System.Drawing.Size(95, 19); + this.FPSStatusLabel.Text = "FPS Placeholder"; + // + // MouseCaptureStatusLabel + // + this.MouseCaptureStatusLabel.Name = "MouseCaptureStatusLabel"; + this.MouseCaptureStatusLabel.Size = new System.Drawing.Size(108, 19); + this.MouseCaptureStatusLabel.Text = "Mouse Placeholder"; + // + // ProgressBar + // + this.ProgressBar.Name = "ProgressBar"; + this.ProgressBar.Size = new System.Drawing.Size(100, 18); + this.ProgressBar.Visible = false; + // + // DWindow + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoSize = true; + this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.ClientSize = new System.Drawing.Size(1113, 972); + this.Controls.Add(this.UIPanel); + this.Controls.Add(this.SystemMenu); + this.DoubleBuffered = true; + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.MainMenuStrip = this.SystemMenu; + this.MaximizeBox = false; + this.Name = "DWindow"; + this.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide; + this.Text = "Darkstar"; + this.Deactivate += new System.EventHandler(this.OnWindowDeactivate); + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.OnWindowClosed); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.OnKeyDown); + this.KeyUp += new System.Windows.Forms.KeyEventHandler(this.OnKeyUp); + this.Leave += new System.EventHandler(this.OnWindowLeave); + this.SystemMenu.ResumeLayout(false); + this.SystemMenu.PerformLayout(); + this.UIPanel.ResumeLayout(false); + this.UIPanel.PerformLayout(); + this.SystemStatus.ResumeLayout(false); + this.SystemStatus.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.MenuStrip SystemMenu; + private System.Windows.Forms.ToolStripMenuItem SystemToolStripMenuItem; + private System.Windows.Forms.Panel UIPanel; + private System.Windows.Forms.StatusStrip SystemStatus; + private System.Windows.Forms.ToolStripStatusLabel ExecutionStatusLabel; + private System.Windows.Forms.Panel DisplayBox; + private System.Windows.Forms.ToolStripMenuItem floppyToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem FloppyLoadToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem FloppyUnloadToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem HardDiskToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem StartToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem ResetToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem LoadHardDiskToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem NewHardDiskToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem ConfigurationToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem ExitToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem HelpToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem AlternateBootToolStripMenuItem; + private System.Windows.Forms.ToolStripStatusLabel MPStatusLabel; + private System.Windows.Forms.ToolStripStatusLabel FPSStatusLabel; + private System.Windows.Forms.ToolStripMenuItem showDebuggerToolStripMenuItem; + private System.Windows.Forms.ToolStripStatusLabel MouseCaptureStatusLabel; + private System.Windows.Forms.ToolStripMenuItem FloppyLabelToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem HardDiskLabelToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem NewSA1004ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem aboutToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem NewQ2040ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem NewQ2080ToolStripMenuItem; + private System.Windows.Forms.ToolStripProgressBar ProgressBar; + private System.Windows.Forms.ToolStripMenuItem ViewDocumentationToolStripMenuItem; + } +} \ No newline at end of file diff --git a/D/UI/DWindow.cs b/D/UI/DWindow.cs new file mode 100644 index 0000000..25f8eec --- /dev/null +++ b/D/UI/DWindow.cs @@ -0,0 +1,584 @@ +/* + BSD 2-Clause License + + Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +using D.Debugger; +using D.IO; +using D.IOP; +using D.Logging; +using SDL2; +using System; +using System.Diagnostics; +using System.IO; +using System.Windows.Forms; + +namespace D.UI +{ + /// + /// DWindow is the main user interface window for the emulator. + /// The DWindow-IO file contains the portion of this class that implements the + /// display, mouse, and keyboard. + /// This file contains the UI-related code for controlling the emulation. + /// + public partial class DWindow : Form + { + public DWindow(DSystem system) + { + _system = system; + + InitializeComponent(); + InitializeIO(); + + PopulateAltBoot(); + + _frameTimer = new System.Windows.Forms.Timer(); + _frameTimer.Interval = 1000; + _frameTimer.Tick += OnFrameTimerTick; + _frameTimer.Start(); + + _system.ExecutionStateChanged += OnExecutionStateChanged; + _system.IOP.MiscIO.MPChanged += OnMPCodeChanged; + + // + // Load any disks referenced by the configuration. + // + if (!string.IsNullOrWhiteSpace(Configuration.FloppyDriveImage)) + { + try + { + _system.IOP.FloppyController.Drive.LoadDisk(new FloppyDisk(Configuration.FloppyDriveImage)); + } + catch(Exception e) + { + Log.Write(LogType.Error, LogComponent.Configuration, "Unable to load floppy image {0}. Error {1}", + Configuration.FloppyDriveImage, + e.Message); + } + } + + if (!string.IsNullOrWhiteSpace(Configuration.HardDriveImage)) + { + try + { + _system.HardDrive.Load(Configuration.HardDriveImage); + } + catch (Exception e) + { + Log.Write(LogType.Error, LogComponent.Configuration, "Unable to load hard drive image {0}. Error {1}", + Configuration.FloppyDriveImage, + e.Message); + } + } + + UpdateUIRunState(); + UpdateMPCode(); + UpdateHardDriveLabel(); + UpdateFloppyDriveLabel(); + UpdateMouseState(); + } + + // + // UI Event handlers: + // + private void StartToolStripMenuItem_Click(object sender, EventArgs e) + { + if (!_system.IsExecuting) + { + SystemExecutionContext context = new SystemExecutionContext(null, null, null, OnExecutionError); + _system.StartExecution(context); + } + else + { + _system.StopExecution(); + } + } + + private void ResetToolStripMenuItem_Click(object sender, EventArgs e) + { + _system.Reset(); + } + + private void FloppyLoadToolStripMenuItem_Click(object sender, EventArgs e) + { + string imagePath = ShowImageLoadDialog(true); + + if (!string.IsNullOrWhiteSpace(imagePath)) + { + _system.IOP.FloppyController.Drive.LoadDisk(new FloppyDisk(imagePath)); + Configuration.FloppyDriveImage = imagePath; + + UpdateFloppyDriveLabel(); + } + } + + private void FloppyUnloadToolStripMenuItem_Click(object sender, EventArgs e) + { + _system.IOP.FloppyController.Drive.UnloadDisk(); + Configuration.FloppyDriveImage = String.Empty; + UpdateFloppyDriveLabel(); + } + + private void LoadHardDiskToolStripMenuItem_Click(object sender, EventArgs e) + { + string imagePath = ShowImageLoadDialog(false); + + if (!string.IsNullOrWhiteSpace(imagePath)) + { + // + // Timer to update progress bar while loading: + // + Timer t = new Timer(); + t.Interval = 100; + t.Enabled = true; + t.Tick += (o, i) => + { + if (ProgressBar.Value >= ProgressBar.Maximum) + { + ProgressBar.Value = 0; + } + ProgressBar.Increment(10); + }; + + // + // Do load asynchronously. UI is disabled during this time. + // + System.Threading.ThreadPool.QueueUserWorkItem( + delegate + { + BeginInvoke((MethodInvoker)delegate + { + // + // Set up status bar + // + ProgressBar.Visible = true; + ProgressBar.Style = ProgressBarStyle.Blocks; + ProgressBar.Value = 0; + ProgressBar.Minimum = 0; + ProgressBar.Maximum = 100; + + // + // Disable UI + // + SystemMenu.Enabled = false; + }); + + // + // Pause the system if it's running. + // + bool isRunning = _system.IsExecuting; + SystemExecutionContext context = _system.ExecutionContext; + + if (isRunning) + { + _system.StopExecution(); + } + + DialogResult res = DialogResult.Yes; + try + { + // Commit current image to disk. + _system.HardDrive.Save(); + } + catch (Exception ex) + { + res = MessageBox.Show( + String.Format("Unable to save current hard drive's contents. Error: {0}. Continue loading new drive image?", ex.Message), + "Error:", + MessageBoxButtons.YesNo); + } + + if (res == DialogResult.Yes) + { + + try + { + // Load new image. + _system.HardDrive.Load(imagePath); + } + catch (Exception ex) + { + MessageBox.Show( + String.Format("Unable to load drive image. Error: {0}", ex.Message), + "Error:"); + } + + Configuration.HardDriveImage = imagePath; + } + + // + // Restart the system if necessary + // + if (isRunning) + { + _system.StartExecution(context); + } + + // + // Hide status text, re-enable UI + // + this.BeginInvoke((MethodInvoker)delegate + { + t.Stop(); + ProgressBar.Visible = false; + SystemMenu.Enabled = true; + + UpdateHardDriveLabel(); + }); + + }); + } + } + + private void NewHardDiskToolStripMenuItem_Click(object sender, EventArgs e) + { + // Ask for the new image's path. + string imagePath = ShowNewImageDialog(false); + + if (!string.IsNullOrWhiteSpace(imagePath)) + { + D.IO.DriveType type = (D.IO.DriveType)((ToolStripMenuItem)sender).Tag; + _system.HardDrive.NewDisk(type, imagePath); + + UpdateHardDriveLabel(); + } + } + + private void OnAltBootOptionClick(object sender, EventArgs e) + { + ToolStripMenuItem item = (ToolStripMenuItem)sender; + _system.IOP.MiscIO.AltBoot = (AltBootValues)item.Tag; + + // Uncheck all items, check the selected one. + foreach(ToolStripMenuItem m in AlternateBootToolStripMenuItem.DropDownItems) + { + m.Checked = false; + } + + item.Checked = true; + } + + private void ConfigurationToolStripMenuItem_Click(object sender, EventArgs e) + { + ConfigurationDialog configDialog = new ConfigurationDialog(); + configDialog.MemorySize = Configuration.MemorySize; + configDialog.HostID = Configuration.HostID; + configDialog.HostPacketInterfaceName = Configuration.HostPacketInterfaceName; + configDialog.ThrottleSpeed = Configuration.ThrottleSpeed; + configDialog.DisplayScale = Configuration.DisplayScale; + configDialog.SlowPhosphor = Configuration.SlowPhosphor; + configDialog.TODDateTime = Configuration.TODDateTime; + configDialog.TODDate = Configuration.TODDate; + configDialog.TODSetMode = Configuration.TODSetMode; + + DialogResult res = configDialog.ShowDialog(this); + + if (res == DialogResult.OK) + { + if (_system.IsExecuting && + configDialog.MemorySize != Configuration.MemorySize) + { + MessageBox.Show("Changes to system memory size will not take effect until the system is restarted."); + } + + if (configDialog.HostID != Configuration.HostID) + { + Configuration.HostID = configDialog.HostID; + + // Break substitution. + ((IOPMemoryBus)_system.IOP.Memory).UpdateHostIDProm(); + } + + if (configDialog.HostPacketInterfaceName != Configuration.HostPacketInterfaceName) + { + MessageBox.Show("Changes to ethernet interface will not take effect until the system is restarted."); + } + + if (configDialog.TODSetMode != Configuration.TODSetMode || + configDialog.TODDateTime != Configuration.TODDateTime || + configDialog.TODDate != Configuration.TODDate) + { + _system.IOP.MiscIO.TODClock.PowerUpSetMode = configDialog.TODSetMode; + _system.IOP.MiscIO.TODClock.PowerUpSetTime = + (configDialog.TODSetMode == TODPowerUpSetMode.SpecificDateAndTime) ? + configDialog.TODDateTime : configDialog.TODDate; + _system.IOP.MiscIO.TODClock.ResetTODClockTime(); + + if (_system.IsExecuting) + { + MessageBox.Show("Changes to system time may not take effect until the system is restarted."); + } + } + + Configuration.MemorySize = configDialog.MemorySize; + Configuration.HostPacketInterfaceName = configDialog.HostPacketInterfaceName; + Configuration.ThrottleSpeed = configDialog.ThrottleSpeed; + Configuration.DisplayScale = configDialog.DisplayScale; + Configuration.SlowPhosphor = configDialog.SlowPhosphor; + Configuration.TODDateTime = configDialog.TODDateTime; + Configuration.TODDate = configDialog.TODDate; + Configuration.TODSetMode = configDialog.TODSetMode; + + UpdateDisplayScale(); + UpdateSlowPhosphor(); + } + } + + private void ShowDebuggerToolStripMenu_Click(object sender, EventArgs e) + { + if (_debuggerWindow == null) + { + _debuggerWindow = new DebuggerMain( + _system, + DebuggerReason.UserInvoked, + "*** Welcome to the Debugger! ***"); + _debuggerWindow.Show(this); + _debuggerWindow.FormClosed += OnDebuggerWindowClosed; + } + } + + private void ExitToolStripMenuItem_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void ViewDocumentationToolStripMenu_Click(object sender, EventArgs e) + { + Process.Start(_readmeFilename); + } + + private void aboutToolStripMenuItem_Click(object sender, EventArgs e) + { + AboutBox aboutBox = new AboutBox(); + aboutBox.ShowDialog(this); + } + + private void OnDebuggerWindowClosed(object sender, FormClosedEventArgs e) + { + _debuggerWindow = null; + + // + // Reattach our context if the system is currently running. + // + if (_system.IsExecuting) + { + _system.StopExecution(); + + SystemExecutionContext context = new SystemExecutionContext(null, null, null, OnExecutionError); + _system.StartExecution(context); + } + } + + private void OnWindowClosed(object sender, FormClosedEventArgs e) + { + // + // Ensure emulation is stopped. + // + _system.StopExecution(); + + // + // Stop the SDL thread. + // + SDL.SDL_Event closeEvent = new SDL.SDL_Event(); + closeEvent.type = SDL.SDL_EventType.SDL_QUIT; + SDL.SDL_PushEvent(ref closeEvent); + + _sdlThread.Join(1000); + + // + // Commit config back to storage. + // + Configuration.WriteConfiguration(); + + DialogResult = DialogResult.OK; + } + + private void OnFrameTimerTick(object sender, EventArgs e) + { + FPSStatusLabel.Text = String.Format("{0} Fields/Sec ({1}%)", + _frameCount, + (int)((_frameCount / 77.4) * 100.0)); + + _frameCount = 0; + } + + private void OnExecutionError(Exception e) + { + if (_debuggerWindow == null) + { + _debuggerWindow = new DebuggerMain( + _system, + DebuggerReason.Error, + String.Format("*** Execution error: {0} ***", e.Message)); + BeginInvoke(new DisplayDelegate(DisplayDebugger)); + } + } + + private void OnMPCodeChanged() + { + BeginInvoke(new DisplayDelegate(UpdateMPCode)); + } + + private void OnExecutionStateChanged() + { + BeginInvoke(new DisplayDelegate(UpdateUIRunState)); + } + + private void DisplayDebugger() + { + _debuggerWindow.Show(this); + _debuggerWindow.FormClosed += OnDebuggerWindowClosed; + } + + private void UpdateMPCode() + { + if (_system.IOP.MiscIO.MPanelBlank) + { + MPStatusLabel.Text = "MP: ----"; + } + else + { + MPStatusLabel.Text = String.Format("MP: {0:d4}", _system.IOP.MiscIO.MPanelValue); + } + } + + private void UpdateUIRunState() + { + StartToolStripMenuItem.Text = _system.IsExecuting ? "Stop" : "Start"; + ExecutionStatusLabel.Text = _system.IsExecuting ? "System is running." : "System is stopped."; + } + + private void UpdateMouseState() + { + MouseCaptureStatusLabel.Text = _mouseCaptured ? + "Mouse captured. Press Alt to release." : + "Click on the display to capture mouse/keyboard."; + } + + private void UpdateHardDriveLabel() + { + HardDiskLabelToolStripMenuItem.Text = + String.Format("{0} ({1})", + Path.GetFileName(_system.HardDrive.ImagePath), + _system.HardDrive.Type); + + HardDiskLabelToolStripMenuItem.ToolTipText = _system.HardDrive.ImagePath; + } + + private void UpdateFloppyDriveLabel() + { + FloppyLabelToolStripMenuItem.Text = + _system.IOP.FloppyController.Drive.IsLoaded ? + Path.GetFileName(_system.IOP.FloppyController.Drive.Disk.ImagePath) : + "No Floppy Loaded"; + + FloppyLabelToolStripMenuItem.ToolTipText = _system.IOP.FloppyController.Drive.IsLoaded ? + String.Format("{0}\r\n{1}", + _system.IOP.FloppyController.Drive.Disk.ImagePath, + _system.IOP.FloppyController.Drive.Disk.Description) : + "No Floppy Loaded"; + } + + private void PopulateAltBoot() + { + for (AltBootValues v = AltBootValues.None; v < AltBootValues.HeadCleaning; v++) + { + ToolStripMenuItem item = new ToolStripMenuItem(v.ToString()); + item.Tag = (object)v; + item.Click += OnAltBootOptionClick; + + // Check the item if it's the current alt boot value + item.Checked = (v == _system.IOP.MiscIO.AltBoot); + + AlternateBootToolStripMenuItem.DropDownItems.Add(item); + } + } + + private string ShowImageLoadDialog(bool floppy) + { + OpenFileDialog fileDialog = new OpenFileDialog(); + + fileDialog.DefaultExt = floppy ? "imd" : "img"; + fileDialog.Filter = floppy ? _floppyDiskFilter : _hardDiskFilter; + fileDialog.Multiselect = false; + fileDialog.CheckFileExists = true; + fileDialog.CheckPathExists = true; + fileDialog.Title = String.Format("Select disk image to load."); + + DialogResult res = fileDialog.ShowDialog(); + + if (res == DialogResult.OK) + { + return fileDialog.FileName; + } + else + { + return null; + } + } + + private string ShowNewImageDialog(bool floppy) + { + SaveFileDialog fileDialog = new SaveFileDialog(); + + fileDialog.DefaultExt = floppy ? "imd" : "img"; + fileDialog.Filter = floppy ? _floppyDiskFilter : _hardDiskFilter; + fileDialog.OverwritePrompt = true; + fileDialog.ValidateNames = true; + fileDialog.CheckPathExists = true; + fileDialog.Title = String.Format("Select path for new disk image."); + + DialogResult res = fileDialog.ShowDialog(); + + if (res == DialogResult.OK) + { + return fileDialog.FileName; + } + else + { + return null; + } + } + + // + // Timer for FPS display + // + private System.Windows.Forms.Timer _frameTimer; + + // + // Debugger window reference + // + private DebuggerMain _debuggerWindow; + + // + // TODO: Move to string resources + // + private const string _hardDiskFilter = "Star Hard Disk Images (*.img)|*.img|All Files(*.*)|*.*"; + private const string _floppyDiskFilter = "Star Floppy Disk Images (*.imd)|*.imd|All Files(*.*)|*.*"; + private const string _readmeFilename = "readme.txt"; + } +} diff --git a/D/UI/DWindow.resx b/D/UI/DWindow.resx new file mode 100644 index 0000000..7e5c732 --- /dev/null +++ b/D/UI/DWindow.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 132, 17 + + + + + AAABAAIAICAAAAAACACoCAAAJgAAABAQAAAAAAgAaAUAAM4IAAAoAAAAIAAAAEAAAAABAAgAAAAAAAAE + AAAAAAAAAAAAAAABAAAAAQAAAAAAAEJCQgBYWFgAY2JjAG5tbgB5eHgAhoaGAJGRkQCamZkApKOkAK+u + rwC6uboAxcTFAPHv8AD29vYA+fn5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////AA4ODg4ODg4ODg4ODgAAAAAAAA4ODg4ODg4ODg4ODg4ODg4ODg4O + Dg4ODg4OAAAAAAAADg4ODg4ODg4ODg4ODg4ODg4OAAAAAAAAAAAAAAAAAAAAAAAAAAAODg4ODg4ODg4O + Dg4AAAAAAAAAAAAAAAAAAAAAAAAAAA4ODg4ODg4ODg4AAAAAAAAAAAAADg4AAA4ODg4AAAAAAAAODg4O + Dg4ODgAAAAAAAAAAAAAODgAADg4ODgAAAAAAAA4ODg4ODgAAAAAAAAAAAAAODg4OAAAODg4ODg4AAA8P + AAAAAA4OAAAAAAAAAAAAAA4ODg4AAA4ODg4ODgAADw8AAAAADg4AAAAAAAAAAA4ODg4MDAAADg4ODg4O + Dg4AAAAAAAAAAAAAAAAAAAAADg4ODgwMAAAODg4ODg4ODgAAAAAAAAAADg4ODg4ODg4ODg4OAAAMDA4O + Dg4ODg4ODg4ODgAAAAAODg4ODg4ODg4ODg4AAAwMDg4ODg4ODg4ODg4OAAAAAA4ODg4ODg4ODg4ODgAA + Dg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4OAAAODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O + Dg4AAA4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODgAADg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4O + Dg4ODg4OAAAODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4AAA4ODg4ODg4ODg4ODg4ODg4ODg4O + Dg4ODg4ODg4ODgAADg4ODg4ODg4ODg4OAAAODg4ODg4ODg4ODg4ODg4OAAAODg4ODg4ODg4ODg4AAA4O + Dg4ODg4ODg4ODg4ODg4AAA4ODg4ODg4ODg4AAAAAAAAODg4ODg4ODg4ODg4ODgAADg4ODg4ODg4ODgAA + AAAAAA4ODg4ODg4ODg4ODg4OAAAPDw4ODg4ODgAAAAAAAAAAAAAODg4ODg4ODg4ODg4AAA8PDg4ODg4O + AAAAAAAAAAAAAA4ODg4ODg4ODg4ODg4OAAAODg4ODg4AAAAAAAAAAAAADg4ODg4ODg4ODg4ODg4AAA4O + Dg4ODgAAAAAAAAAAAAAODg4ODg4ODg4ODg4ODgAADg4ODg4ODAwAAAAAAAAODg4ODg4ODg4ODg4ODg4O + AAAODg4ODg4MDAAAAAAAAA4ODg4ODg4ODg4ODg4ODg4AAAAADAwMDAAADAwAAA4ODg4ODg4ODg4ODg4O + Dg4ODgAAAAAMDAwMAAAMDAAADg4ODg4ODg4ODg4ODg4ODg4ODg4MDAAAAAAMDA4ODg4ODg4ODg4ODg4O + Dg4ODg4ODg4ODgwMAAAAAAwMDg4ODg4ODg4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAA + AAAAAAAAAAEAAAABAAAAAP8AAP8AAP8AAAAA//8A//8AAP8A/wDAwMAA//jwANfr+gDU/38A/wAAAOIr + igAqKqUAAAAAAAQEBAAICAgADAwMABEREQAWFhYAHBwcACIiIgApKSkAMzMzADk5OQBCQkIATU1NAFVV + VQBgYGAAZmZmAHBwcACAgIAAjIyMAJSUlACZmZkApKSkAKysrAC2trYAwMDAAMzMzADU1NQA2traAODg + 4ADs7OwA+Pj4APv7+wD///8AMwAAAGYAAACZAAAAzAAAAP8AAAAAMwAAMzMAAGYzAACZMwAAzDMAAP8z + AAAAZgAAM2YAAGZmAACZZgAAzGYAAP9mAAAAmQAAM5kAAGaZAACZmQAAzJkAAP+ZAAAAzAAAM8wAAGbM + AACZzAAAzMwAAP/MAAAA/wAAM/8AAGb/AACZ/wAAzP8AAP//AAAAADMAMwAzAGYAMwCZADMAzAAzAP8A + MwAAMzMAZjMzAJkzMwDMMzMA/zMzAABmMwAzZjMAZmYzAJlmMwDMZjMA/2YzAACZMwAzmTMAZpkzAJmZ + MwDMmTMA/5kzAADMMwAzzDMAZswzAJnMMwDMzDMA/8wzAAD/MwAz/zMAZv8zAJn/MwDM/zMA//8zAAAA + ZgAzAGYAZgBmAJkAZgDMAGYA/wBmAAAzZgAzM2YAZjNmAJkzZgDMM2YA/zNmAABmZgAzZmYAmWZmAMxm + ZgD/ZmYAAJlmADOZZgBmmWYAmZlmAMyZZgD/mWYAAMxmADPMZgBmzGYAmcxmAMzMZgD/zGYAAP9mADP/ + ZgBm/2YAmf9mAMz/ZgD//2YAAACZADMAmQBmAJkAmQCZAMwAmQD/AJkAADOZADMzmQBmM5kAmTOZAMwz + mQD/M5kAAGaZADNmmQBmZpkAmWaZAMxmmQD/ZpkAAJmZADOZmQBmmZkAzJmZAP+ZmQABzJkAM8yZAGbM + mQCZzJkAzMyZAP/MmQAA/5kAM/+ZAGb/mQCZ/5kAzP+ZAP//mQAAAMwAMwDMAGYAzACZAMwAzADMAP8A + zAAAM8wAMzPMAGYzzACZM8wAzDPMAP8zzAAAZswAM2bMAGZmzACZZswAzGbMAP9mzAAAmcwAM5nMAGaZ + zACZmcwAzJnMAP+ZzAAAzMwAM8zMAGbMzACZzMwA/8zMAAD/zAAz/8wAZv/MAJn/zADM/8wA///MAAAA + /wAzAP8AZgD/AJkA/wDMAP8A/wD/AAAz/wAzM/8AZjP/AJkz/wDMM/8A/zP/AABm/wAzZv8AZmb/AJlm + /wDMZv8A/2b/AACZ/wAzmf8AZpn/AJmZ/wDMmf8A/5n/AADM/wAzzP8AZsz/AJnM/wDMzP8A/8z/AAD/ + /wAz//8AZv//AJn//wDM//8ALCwsLCwsDg4OLCwsLCwsLCwsDg4ODg4ODg4ODiwsLCwsDg4ODg4sDiws + Dg4OLCwsDg4ODg4sLA4sLCwODg4OLA4ODg4sLCwOLCwsLA4ODg4sLCwsLCwOLCwsLCwsLA4OLCwsLCws + DiwsLCwsLCwsLCwsLCwsLA4sLCwsLCwsLCwsLCwsLCwOLCwsLCwsLCwsLCwsLCwsDiwsLCwsLA4sLCws + LCwsLA4sLCwsLA4ODiwsLCwsLCwOLCwsLA4ODg4OLCwsLCwsLA4sLCwODg4ODiwsLCwsLCwOLCwsLA4O + DiwsLCwsLCwsDg4sLA4sDiwsLCwsLCwsLCwsDg4sLCwsLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= + + + \ No newline at end of file diff --git a/D/app.manifest b/D/app.manifest new file mode 100644 index 0000000..6c647ae --- /dev/null +++ b/D/app.manifest @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + diff --git a/D/packages.config b/D/packages.config new file mode 100644 index 0000000..55babff --- /dev/null +++ b/D/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/D/readme.txt b/D/readme.txt new file mode 100644 index 0000000..a356026 --- /dev/null +++ b/D/readme.txt @@ -0,0 +1,558 @@ +Readme.txt for Darkstar v1.0: + +1. Introduction and Overview +============================ + +Darkstar provides emulation of the Xerox Dandelion workstation, commonly known +as the Star, 8010, or 1108. + +To avoid confusion in the rest of this document, the name "Star" will be +used to refer to any of the above machines. + +1.1 What's Emulated +------------------- + +Darkstar currently emulates the following Star hardware: + - Standard 8010/1108 Central Processor (CP) with 4KW of microcode store + - i8085-based IO Processor (IOP) + - Up to 1MW of main memory + - Bitmapped Display + - Keyboard / Mouse + - 10, 40, or 80mb hard drives (SA1000 interface) + - 8 inch floppy drive (read-only) + - 10mbit Ethernet + - Real-time clock + +1.2 What's Not +-------------- + +At this time, the below are not emulated by Darkstar: + - Writing to floppy disks + - Sound + - Serial ports + - The LSEP printer interface + +2.0 Requirements +================ + +Darkstar will run on any Windows PC running Windows Vista or later, with version +4.5.3 or later of the .NET Framework installed. .NET should be present by +default on Windows Vista and later; if it is not installed on your computer it +can be obtained at https://www.microsoft.com/net. + +Darkstar will also run under Mono (http://www.mono-project.com/) on Unix +platforms. macOS support will be present in a future release. SDL 2.0 is used +for the emulated display -- use your operating system's package manager to +ensure this is installed. + +The Star keyboard has many keys not present on modern keyboards. Many of +these are mapped to Function keys, arrow keys, and the Home/End/PgUp/PgDown +keys present on most desktop keyboards -- laptop keyboards may be more +difficult to use, depending on your keyboard's layout. + +A three-button mouse is essential for using some Star software (XDE and +Interlisp-D, for example). On most mice, the mousewheel can be clicked to +provide the third (middle) button. Laptops with trackpads may have +configuration options to simulate three buttons but will likely be clumsy to +use. + +If you wish to make use of the emulated Star's Ethernet interface, you +will need to have WinPCAP installed (on Windows) or libpcap (on the Unix of +your choice). WinPCAP can be downloaded from http://www.winpcap.org/. + + +3.0 Getting Started +=================== + +Installation of Darkstar on Windows is simple: Double-click the installer +file, named "DarkstarSetup.msi" and follow the on-screen instructions. The +installer will install all of the necessary files and create two icons on your +Start menu, one for Darkstar itself, and one for its documentation (the file +you're reading now!) + +On Unix platforms, extract the Darkstar-mono.zip archive in a directory of your +choosing. + + +3.1 Using Darkstar +================== + +On Windows, Darkstar can be started by clicking on the "Darkstar" icon created +by the installer. On Unix, Darkstar can be started from a shell prompt by +running "mono Darkstar.exe" in the directory chosen in Section 3.0. + +Once started, the main Darkstar window will appear. This window +is your primary means of interaction with the emulated Star workstation. + + +3.1.1 The Display +----------------- + +The large (initially black) area is the Star's display. Clicking anywhere in +this area while the Star system is running will "capture" the mouse and +keyboard: your mouse movements and keyboard inputs will be sent to the Star, +and mouse movements will be restricted to the Darkstar window. To release the +capture, press either "Alt" key on your keyboard. + + +3.1.2 The Status Bar +-------------------- + +At the bottom of the Darkstar window is the Status Bar. This shows information +about the system. From left to right: + +- The current MP (Maintenance Panel) code. On a real Star, this is a 4 digit + LED display on the front of the CPU unit. The number displayed is used to + communicate boot status and diagnostic information to the user. If the + display reads "----" this indicates that the Star has turned the MP display + off or it has not been initialized. A comprehensive list of MP codes can be + found on Bitsavers at + http://bitsavers.org/pdf/xerox/8010_dandelion/Dandelion_MPCodes_Mar85.pdf. + +- The System status: Stopped or Running. + +- The Emulation speed: In fields per second and as a percentage of a real + Star's execution speed. 78 fields/sec is approximately 100%. + +- Mouse Capture status: Indicates whether the mouse is currently captured. + + +3.1.3 The System Menu +--------------------- + +The System menu allows you to control the Star system and the emulator. The +items in the menu are enumerated below. + +Start / Stop - This will start the Star system running if it is stopped, and + stop it if it is already running. + +Reset - This will reset the Star. This is equivalent to pressing the "B Reset" + button on a real Star. + +Alternate Boot - Allows selection of an alternate boot device. On a real Star, + this is accomplished by holding down the Alt Boot button during + power-up until the appropriate code appears in the MP display. + Selecting a device in this menu will simulate holding the Alt Boot + button as appropriate to select the boot device. + + In general you won't need to change this unless you are installing or + performing maintenance on an operating system. However: Selecting + "Rigid" rather than the default ("DiagnosticRigid") can save time + when booting ViewPoint or XDE. + +Floppy Disk - Allows loading or unloading of floppy disk images. If an image + is currently loaded, its name will be displayed in the space at the + bottom of the submenu; hovering over this space will show the full + path to the image and image metadata, if present. Darkstar uses + floppy disk images in ImageDisk (.IMD) format. + See: http://www.classiccmp.org/dunfield/img/index.htm for details. + +Hard Disk - Allows loading or creating new hard disk images, which typically + have a ".IMG" file extension. If an image is currently loaded, its + name will be displayed in the space at the bottom of the submenu; + hovering over this space will show the full path to the image. + See Section 9.0 for information on the image format. + +Configuration - Invokes the Configuration dialog. See Section 4.0 for more + details on configuration options. + +Show Debugger - Invokes the Debugger interface. See section 5.0 for more + details on care and feeding. + +Exit - Quits Darkstar. Contents of loaded hard disk images are saved to the + image files they were loaded from. + + +3.2 The Keyboard +---------------- + +The Star's keyboard has many keys that are not present on a standard PC +keyboard. Darkstar maps F1-F12, the arrow keys, and the home/end/pgup/pgdown +keys to these special keys, as below: + +Star Key PC Key +---------------------- +Again F1 +Delete F2 +Find F3 +Copy F4 +Same F5 +Move F6 +Open F7 or Left Control +Props F8 or Right Control +Center F9 +Bold F10 +Italics F11 +Underline F12 +Superscript Print Screen +Subscript Scroll Lock +Larger/Smaller Pause +Defaults Num Lock +Skip/Next Home +Undo Page Up +Defn/Expand End +Stop Page Down +Help Up Arrow +Margins Left Arrow +Font Backslash +Keyboard Down + + +3.3 Software +------------ + +3.3.1 Getting Software and Documentation +---------------------------------------- + +Darkstar does not come with any media. Bitsavers has floppy disk sets for +ViewPoint, XDE, and Interlisp-D at: + +http://bitsavers.org/bits/Xerox/8010/ +and +http://bitsavers.org/bits/Xerox/1108/ + +These can be used to bootstrap a fresh installation onto a virtual hard disk. +Note that at this time, only floppy disk images in ImageDisk format (.IMD) are +supported by Darkstar. + +Pre-built hard disk images suitable for use with Darkstar are available at: + +http://bitsavers.org/bits/Xerox/8010/8010_hd_images.zip + + +Documentation for the above operating systems is available at: + +http://bitsavers.org/pdf/xerox/viewpoint +http://bitsavers.org/pdf/xerox/interlisp-d/ +and +http://bitsavers.org/pdf/xerox/xde/ + + +3.3.2 Booting from a Hard Disk +------------------------------ + +If you have an existing hard disk image, you can boot from it by first loading +the image using the "System->Hard Disk->Load..." menu. This will present a +file dialog allowing you to select the image to load. + +After the image is loaded, use the "System->Start" menu to start the Star +running. During boot, the MP code displayed in the lower-left corner of the +window will display various values indicating status, or in the cases of +failure, a diagnostic code. + +ViewPoint and XDE will run a lengthy set of diagnostics during boot -- these +can be skipped by selecting "Rigid" from the "System->Alternate Boot" menu +before starting or restarting the Star. + + +3.3.3 Installing an Operating System +------------------------------------ + +Covering the proper installation and maintenance of the various Star operating +systems is beyond the scope of this manual, but a few poorly documented and +emulator-specific bits of advice are provided here. + +In general, the manuals listed in Section 3.3.1 are the best starting point and +are not too difficult to understand. + +To boot from an OS installation or diagnostic floppy, load the appropriate +floppy disk image using the "System->Floppy Disk->Load..." menu. Then select +the "Floppy" Alternate Boot item from the "System->Alternate Boot" menu and +start or reset the emulated Star system. The system should then boot from the +floppy disk. + +When starting the installation of a new operating system from scratch, there +are a few steps that are not well documented and which are fairly unintuitive: + + 1) In general it is useful to have the time and date set in the Star's TOD + clock before booting. Many Star operating systems and installers + *really* want to know what time it is, and they don't trust you to type + it in. If the TOD has an invalid time / date it will attempt to get it + from the network and in many cases will not proceed until the network + responds. Unless you have an XNS timeserver running on your network + (you probably do not), use the Configuration dialog to set the time + before booting (See section 4.0). + + 2) If you are starting with a new unformatted hard disk the installer will + hang waiting for the disk microcode to read the disk, usually after + printing the initial banner ("Installer Version X.Y of DD-MMM-YY + HH:MM:SS, etc."). It will sit here indefinitely. + + To get past this, you will need to boot the Diagnostic floppy (usually + provided with each set of installation floppies) and use the diagnostics + to format the disk. This is still more complicated than it should be + due to the way the disk microcode interacts with an unformatted disk. + After booting the diagnostic floppy you will be prompted to enter + timezone and time / date information. + + After entering this information, the diagnostic will print something + similar to: + XX Megabyte Storage Diagnostic Program 8.0 of 11-Mar-88 11:16:45 PST + >Fault Analysis + + And then it will pause for 30-45 seconds and fail with: + Fatal error: Microcode. + + After which the system halts and will not respond to input. + + This is because the Fault Analysis step is expecting a formatted disk + and your disk is not yet formatted. The disk microcode is unable to + cope and goes off into the weeds. + + To work around this, when the ">Fault Analysis" line appears during + boot, hit the "Stop" key on the Star's keyboard (this is mapped to + "Page Down.") The diagnostic will print: + key acknowledged + Command stopped + + And leave you at the ">" prompt. Now you can format your disk! + + Or can you? Typing a "?" will give you a list of available commands + but there's nothing related to disk formatting in that list! + + Xerox didn't want the average person to be able to format disks so this + functionality is hidden by default. To enable it, you use the Logon + command -- Type "Logon" and hit return, and it will ask you for a user + name. Use "Xerox" and then provide the password "wizard" (or "elf", + depending on your stature.) Your privileges will be upgraded and now + "?" will reveal a host of fun commands! The "Format" command is what + you want, and is mostly self-explanatory. Do *not* save the bad page + table (as there isn't one, and the microcode will hang trying to read + it.) Formatting will take several minutes after which you will be + asked if you want to recreate the bad page table (say "yes."). You + will given the option to do a media scan (you can if you want, but + emulated disks have no bad spots so there isn't much point.) + + Once the disk has been formatted, you can boot the Installer disk and + go about doing the actual installation. + + 3) Yes, it really does take ViewPoint 10-15 minutes to boot the first time. + It's not particularly swift on subsequent boots, either. Patience is a + virtue when using a Star. + + 4) If you get stuck at MP Code 937 during boot, first try the advice in + (1) above. + Setting dates post-Y2K may cause issues with some operating systems. + On Viewpoint you might also want to install the Set Time utility + (see the official Viewpoint docs and installer help for details). + + 5) The default startup diagnostics that run during Viewpoint or XDE + boot may fail the RTC test (with flashing MP code 0323 / 0007). + This occurs if the emulated Star is not running at 100% speed -- + either because Throttling is off (See Section 4.1) or because your + computer isn't capable of running the emulation at full speed. This is + because the emulated Star is running faster or slower than the test + expects relative to the RTC -- the test thinks that the RTC is behaving + incorrectly. In these cases, you can either (1) Enable Throttling + during boot (if the system is running too fast) or (2) use the + "System->Alternate Boot" menu to select "Rigid" rather than "Default" -- + this will bypass startup diagnostics entirely. + + +4.0 Configuration +================= + +Darkstar's configuration dialog can be invoked with the +"System->Configuration..." menu. This is a small window with multiple tabs. +Each tab is explained in detail in the following subsections. + + +4.1 System Configuration +------------------------ + +The System Configuration tab provides configuration for three options: + +- Memory Size (KW): Configures the amount of memory installed in the system, + From 128KW to 1024KW in 128KW increments. This defaults to 768KW. + Changes made here will not take effect until the system is reset. Note + that memory sizes over 768KW (1.5MB) never officially existed on real Star + hardware and may cause issues with some software that isn't prepared for + it. In particular, Interlisp-D releases later than Harmony will crash + after startup. + +- Host ID (MAC Address): Configures the Star's Ethernet MAC Address (also used + as the system's Host ID for licensing.) If you have multiple instances of + the emulator running on the same network, all instances should have unique + MAC addresses, and you'll also want to make sure that no other real devices + on your network have the same MAC address. Note that changing this on + systems running Viewpoint will likely invalidate any previously entered + product factoring (license) keys. + +- Throttle Execution Speed: Checking this box will limit execution speed to + the execution speed of a real Star. When unchecked, the emulation will run + as fast as the host processor allows. + Note: See Section 3.3.3 for potential pitfalls with this option disabled. + + +4.2 Ethernet Configuration +-------------------------- + +The Ethernet Configuration tab allows the selection of the host network interface +to use with Darkstar. If WinPcap or libpcap is unavailable, no interfaces will +be listed. + + +4.3 Display Configuration +------------------------- + +The Display Configuration tab provides options for the emulated Star's display: + +- Slow Phosphor Simulation: If checked, the slow phosphor of a real Star's + display is simulated. This is not necessary for any real purpose but looks + more authentic and incurs no performance penalty. + +- Display Scale: Allows scaling the display by a factor 1, 2, 3 or 4. This is + useful on 4k (or higher) resolution displays with a high DPI. + + +4.4 Time Configuration +---------------------- + +The Time Configuration tab provides options for initializing the Star's TOD +(time of day) clock at the time the emulation is started or reset. This is +primarily useful to aid in working around the absence of XNS time servers, +the lack of which can cause problems with some Star operating systems. + +There are three options for TOD clock initialization: + +- Current time/date: This sets the Star's TOD to the current time/date with + no adjustments or changes. + +- Current time/date with Y2K compensation: This sets the Star's TOD to the + current time/date with 28 years subtracted from the date. This works + around software that's not Y2K compliant while still allowing the calendar + to match up. + +- Specified time/date: This sets the TOD to a specific time and date. This + is useful for working around Viewpoint product factoring (license) key + expiry. + +- Specified date: This sets the TOD to the specified date, using the + current (real) time. This is useful as above, but allows the Star's + clock to be in sync with reality. + +- No change: This leaves the TOD alone at powerup/reset. Use this if you + plan to set the time manually or via XNS, or if you want to + maintain the current time / date across resets. + + +5.0 Debugger +============ + +Darkstar has an integrated debugger that can make use of microcode and IOP +(8085) source code (if available) to aid in debugging. The debugger can be +invoked via the "System->Show Debugger" menu. + +This debugger is extremely crude, and is not user-friendly at all. + +The debugger consists of three windows -- the Console, the CP Debugger, +and the IOP debugger. Commands can be executed in the Console window, and +sources, disassembly and breakpoints can be viewed in the CP and IOP +debugger windows. + +The "?" or "help" command at the Console window will give you a brief +synopsis of the various commands at your disposal. + + +6.0 Known Issues +================ + +- Interlisp-D releases after Harmony do not run properly. +- The Ethernet hardware has not been thoroughly tested. +- Speed throttling is not implemented on Unix platforms. +- SDL is forced to software-rendering mode due to an odd bug that has yet to + be solved. Performance may suffer as a result. + + +7.0 Reporting Bugs +================== + +If you believe you have found a new issue (or have a feature request) please +send an e-mail to joshd@livingcomputers.org or open an issue on the GitHub +site (see Section 8.0) + +When you send a report, please be as specific and detailed as possible: +- What issue are you seeing? +- What software are you running? +- What operating system are you running Darkstar on? +- What are the exact steps needed to reproduce the issue? + +The more detailed the bug report, the more possible it is for me to track down +the cause. + + +8.0 Source Code +=============== + +The complete source code is available under the BSD license on GitHub at: + +https://github.com/livingcomputermuseum/Darkstar + +Contributions are welcome! + + +9.0 Hard Disk Image Format +========================== + +The Star's hard drive controller is implemented in microcode and controls the +drive at a very low level, so the hard drive image format is not simply a dump +of the sector data on the disk. + +The image consists of a single byte header which indicates the type of SA1000 +disk the image contains data for: + + 1 - Shugart SA1004 (10MB) + 2 - Quantum Q2040 (40MB) + 3 - Quantum Q2080 (80MB) + +All other values are currently invalid. The geometry for the above disks are: + + SA1004 - 256 cylinders, 4 heads (or tracks) + Q2040 - 512 cylinders, 8 heads + Q2080 - 1172 cylinders, 7 heads + +Following the header are multiple 5325 word blocks, one for each track on the +disk, starting at cylinder 0, head 0, followed by cylinder 0, track 1 and so +on. Each word in the disk image is 24 bits wide, written in little-endian +order: The most significant 8 bits indicate the type of data in the word, the +low 16 bits are the data word itself: + + 0 - Disk data or unused + 1 - Address mark (for header, label, or data) + 2 - CRC + +The Star's controller divides each track into 16 sectors; each sector +consists of three fields: Header, Label, and Data. Each of these begins with +an Address Mark - 0x1a141 for the Header, 0x1a143 for the Label and Data. +Each of the fields end with two words of CRC (currently always a dummy value of +0x2beef). + +Xerox specified that the Header is two 16-bit words in length, the Label is +12 words, and the Data field is 256 words. However: As the writing of address +marks, data, and CRC are controlled by microcode (which could potentially vary +between revisions of the operating system) it is probably best not to make +assumptions about the positioning and length of the sectors. If you need to +extract data, parse each track, looking for the address marks and CRCs to +delineate the actual data. + + +10.0 Thanks and Acknowledgements +=============================== + +Darkstar would not have been possible without the amazing preservation work of +Bitsavers.org + +Ethernet encapsulation is provided courtesy of SharpPcap, a WinPcap/LibPcap wrapper. +See: https://github.com/chmorgan/sharppcap. + +Display rendering and keyboard/mouse input is provided through SDL 2.0, see: +https://www.libsdl.org/ and is accessed using the SDL2-CS wrapper, see: +https://github.com/flibitijibibo/SDL2-CS. + + +11.0 Change History +=================== + +V1.0 +---- +Initial release. diff --git a/DSetup/DarkstarSetup.wixproj b/DSetup/DarkstarSetup.wixproj new file mode 100644 index 0000000..828729a --- /dev/null +++ b/DSetup/DarkstarSetup.wixproj @@ -0,0 +1,66 @@ + + + + Debug + x86 + 3.10 + bcb1273c-6a17-4afe-88ad-470a3594a009 + 2.0 + DarkstarSetup + Package + DarkstarSetup + + + bin\$(Configuration)\ + obj\$(Configuration)\ + Debug + + + bin\$(Configuration)\ + obj\$(Configuration)\ + + + Debug + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + + + + + Darkstar + {0590465e-1d91-4591-946e-ee3f7d82834b} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + + + + + + + + \ No newline at end of file diff --git a/DSetup/Product.wxs b/DSetup/Product.wxs new file mode 100644 index 0000000..b7ba041 --- /dev/null +++ b/DSetup/Product.wxs @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + SELFFOUND + NEWERFOUND + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DSetup/eula.rtf b/DSetup/eula.rtf new file mode 100644 index 0000000..eda2d00 --- /dev/null +++ b/DSetup/eula.rtf @@ -0,0 +1,227 @@ +{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch31505\stshfloch31506\stshfhich31506\stshfbi31507\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\fbidi \fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;} +{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} +{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} +{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} +{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f41\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f42\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\f44\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f45\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f46\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f47\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\f48\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f49\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f51\fbidi \fswiss\fcharset238\fprq2 Arial CE;}{\f52\fbidi \fswiss\fcharset204\fprq2 Arial Cyr;} +{\f54\fbidi \fswiss\fcharset161\fprq2 Arial Greek;}{\f55\fbidi \fswiss\fcharset162\fprq2 Arial Tur;}{\f56\fbidi \fswiss\fcharset177\fprq2 Arial (Hebrew);}{\f57\fbidi \fswiss\fcharset178\fprq2 Arial (Arabic);} +{\f58\fbidi \fswiss\fcharset186\fprq2 Arial Baltic;}{\f59\fbidi \fswiss\fcharset163\fprq2 Arial (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;} +{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;}{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;} +{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} +{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} +{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} +{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} +{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} +{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} +{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} +{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} +{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} +{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} +{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}}{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0; +\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\*\defchp +\fs22\loch\af31506\hich\af31506\dbch\af31505 }{\*\defpap \ql \li0\ri0\sa160\sl259\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;} +{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* +\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa160\sl259\slmult1 +\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 \fs22\lang1033\langfe1033\loch\f31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused +Normal Table;}}{\*\listtable{\list\listtemplateid-298286586\listsimple{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelspace0\levelindent0{\leveltext\'01*;}{\levelnumbers;}}{\listname ;}\listid-2}}{\*\listoverridetable +{\listoverride\listid-2\listoverridecount1{\lfolevel\listoverrideformat{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat0\levelold\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fbias0 }}\ls1}} +{\*\rsidtbl \rsid411784\rsid5519546\rsid13854512}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\operator Josh Dersch}{\creatim\yr2019\mo1\dy15\hr12\min44} +{\revtim\yr2019\mo1\dy15\hr12\min46}{\version3}{\edmins2}{\nofpages1}{\nofwords218}{\nofchars1246}{\nofcharsws1462}{\vern95}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} +\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect +\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont0\relyonvml0\donotembedlingdata1\grfdocevents0\validatexml0\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors0\horzdoc\dghspace120\dgvspace120\dghorigin1701 +\dgvorigin1984\dghshow0\dgvshow3\jcompress\viewkind1\viewscale100\rsidroot5519546 \nouicompat \fet0{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\sectdefaultcl\sftnbj {\*\pnseclvl1 +\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5 +\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang +{\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 +\fs22\lang1033\langfe1033\loch\af31506\hich\af31506\dbch\af31505\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid411784 \hich\af1\dbch\af31505\loch\f1 Darkstar is open source software. S}{ +\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid5519546 \hich\af1\dbch\af31505\loch\f1 ource code is available at }{\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid5519546\charrsid5519546 +\hich\af1\dbch\af31505\loch\f1 https://github.com/l\hich\af1\dbch\af31505\loch\f1 ivingcomputermuseum/Darkstar}{\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid5519546 . +\par +\par }{\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 \hich\af1\dbch\af31505\loch\f1 BSD 2-Clause License +\par +\par \hich\af1\dbch\af31505\loch\f1 Copyright Vulcan Inc. 2017-2018 and Living Computer}{\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid5519546 \hich\af1\dbch\af31505\loch\f1 s}{\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 +\f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 \hich\af1\dbch\af31505\loch\f1 Museum + Labs 2018 +\par \hich\af1\dbch\af31505\loch\f1 All rights reserved. +\par +\par \hich\af1\dbch\af31505\loch\f1 Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditio\hich\af1\dbch\af31505\loch\f1 ns are met: +\par +\par +\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f3\fs19\cf2\lang9\langfe1033\langnp9 \loch\af3\dbch\af31505\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li720\ri0\nowidctlpar\wrapdefault{\*\pn \pnlvlblt\ilvl0\ls1\pnrnot0\pnf3 {\pntxtb \'b7}} +\faauto\ls1\rin0\lin720\itap0\pararsid5519546 {\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 \hich\af1\dbch\af31505\loch\f1 +Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +\par }\pard \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault{\*\pn \pnlvlcont\ilvl0\ls0\pnrnot0\pndec }\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 +\par {\pntext\pard\plain\ltrpar \rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f3\fs19\cf2\lang9\langfe1033\langnp9 \loch\af3\dbch\af31505\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li720\ri0\nowidctlpar\wrapdefault{\*\pn \pnlvlblt\ilvl0\ls1\pnrnot0\pnf3 {\pntxtb \'b7}} +\faauto\ls1\rin0\lin720\itap0\pararsid5519546 {\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 \hich\af1\dbch\af31505\loch\f1 +Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the f\hich\af1\dbch\af31505\loch\f1 ollowing disclaimer in the documentation and/or other materials provided with the distribution. +\par }\pard \ltrpar\ql \li0\ri0\nowidctlpar\wrapdefault\faauto\rin0\lin0\itap0 {\rtlch\fcs1 \af1\afs19 \ltrch\fcs0 \f1\fs19\cf2\lang9\langfe1033\langnp9\insrsid13854512 \hich\af1\dbch\af31505\loch\f1 +\par \hich\af1\dbch\af31505\loch\f1 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLI\hich\af1\dbch\af31505\loch\f1 +ED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LI +\hich\af1\dbch\af31505\loch\f1 M\hich\af1\dbch\af31505\loch\f1 +ITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +\hich\af1\dbch\af31505\loch\f1 \hich\af1\dbch\af31505\loch\f1 WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.}{\rtlch\fcs1 \af1 \ltrch\fcs0 \f1\lang9\langfe1033\langnp9\insrsid13854512 +\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a +9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad +5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 +b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 +0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 +a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f +c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 +0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 +a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b +4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b +4757e8d3f729e245eb2b260a0238fd010000ffff0300504b03041400060008000000210007b740aaca0600008f1a0000160000007468656d652f7468656d652f +7468656d65312e786d6cec595b8bdb46147e2ff43f08bd3bbe49be2cf1065bb69336bb49889d943cceda636bb2238dd18c776342a0244f7d2914d2d28706fad6 +87521a68a0a12ffd310b1bdaf447f4cc489667ec71f6420aa1640d8b34face996fce39face48ba7aed51449d239c70c2e2965bbe52721d1c8fd898c4d3967b6f +d82f345c870b148f1165316eb90bccdd6bbb9f7e7215ed881047d801fb98efa0961b0a31db2916f9088611bfc26638866b13964448c069322d8e13740c7e235a +ac944ab5628448ec3a318ac0ededc9848cb033942edddda5f31e85d358703930a2c940bac68685c28e0fcb12c1173ca089738468cb8579c6ec78881f09d7a188 +0bb8d0724beacf2dee5e2da29dcc888a2db69a5d5ffd657699c1f8b0a2e64ca607f9a49ee77bb576ee5f01a8d8c4f5eabd5aaf96fb5300341ac14a532eba4fbf +d3ec74fd0cab81d2438bef6ebd5b2d1b78cd7f758373db973f03af40a97f6f03dfef07104503af4029dedfc07b5ebd1278065e81527c6d035f2fb5bb5eddc02b +5048497cb8812ef9b56ab05c6d0e99307ac30a6ffa5ebf5ec99caf50500d7975c929262c16db6a2d420f59d2078004522448ec88c50c4fd008aa3840941c24c4 +d923d3100a6f8662c661b85429f54b55f82f7f9e3a5211413b1869d6921730e11b43928fc34709998996fb39787535c8e9ebd7274f5f9d3cfdfde4d9b393a7bf +66732b5786dd0d144f75bbb73f7df3cf8b2f9dbf7ffbf1edf36fd3a9d7f15cc7bff9e5ab377ffcf92ef7b0e255284ebf7bf9e6d5cbd3efbffeebe7e716efed04 +1de8f0218930776ee163e72e8b608116fef820b998c5304444b768c7538e622467b1f8ef89d040df5a208a2cb80e36e3783f01a9b101afcf1f1a8407613217c4 +e2f1661819c07dc6688725d628dc947369611ecee3a97df264aee3ee2274649b3b40b191e5de7c061a4b6c2e83101b34ef50140b34c531168ebcc60e31b6acee +0121465cf7c928619c4d84f380381d44ac21199203a39a56463748047959d80842be8dd8ecdf773a8cda56ddc5472612ee0d442de487981a61bc8ee602453697 +4314513de07b48843692834532d2713d2e20d3534c99d31b63ce6d36b71358af96f49b2033f6b4efd345642213410e6d3ef710633ab2cb0e831045331b7640e2 +50c77ec60fa144917387091b7c9f9977883c873ca0786bbaef136ca4fb6c35b8070aab535a1588bc324f2cb9bc8e9951bf83059d20aca4061a80a1eb1189cf14 +f93579f7ff3b7907113dfde1856545ef47d2ed8e8d7c5c50ccdb09b1de4d37d6247c1b6e5db803968cc987afdb5d348fef60b855369bd747d9fe28dbeeff5eb6 +b7ddcfef5fac57fa0cd22db7ade9765d6ddea3ad7bf709a174201614ef71b57de7d095c67d189476eab915e7cf72b3100ee59d0c1318b86982948d9330f10511 +e1204433d8e3975de964ca33d753eecc1887adbf1ab6fa96783a8ff6d9387d642d97e5e3692a1e1c89d578c9cfc7e17143a4e85a7df51896bb576ca7ea717949 +40da5e8484369949a26a21515f0eca20a98773089a85845ad97b61d1b4b06848f7cb546db0006a795660dbe4c066abe5fa1e9880113c55218ac7324f69aa97d9 +55c97c9f99de164ca302600fb1ac8055a69b92ebd6e5c9d5a5a5768e4c1b24b4723349a8c8a81ec64334c65975cad1f3d0b868ae9bab941af46428d47c505a2b +1af5c6bb585c36d760b7ae0d34d69582c6ce71cbad557d2899119ab5dc093cfac3613483dae172bb8be814de9f8d4492def097519659c24517f1300db8129d54 +0d222270e25012b55cb9fc3c0d34561aa2b8952b20081f2cb926c8ca87460e926e26194f267824f4b46b2332d2e929287caa15d6abcafcf26069c9e690ee4138 +3e760ee83cb98ba0c4fc7a5906704c38bc012aa7d11c1378a5990bd9aafed61a5326bbfa3b455543e938a2b310651d4517f314aea43ca7a3cef2186867d99a21 +a05a48b2467830950d560faad14df3ae9172d8da75cf369291d34473d5330d55915dd3ae62c60ccb36b016cbcb35798dd532c4a0697a874fa57b5d729b4bad5b +db27e45d02029ec7cfd275cfd110346aabc90c6a92f1a60c4bcdce46cddeb15ce019d4ced32434d5af2dddaec52def11d6e960f0529d1fecd6ab168626cb7da5 +8ab4faf6a17f9e60070f413cbaf022784e0557a9848f0f09820dd140ed4952d9805be491c86e0d3872e60969b98f4b7edb0b2a7e502835fc5ec1ab7aa542c36f +570b6ddfaf967b7eb9d4ed549e4063116154f6d3ef2e7d780d4517d9d71735bef105265abe69bb32625191a92f2c45455c7d812957b67f81710888cee35aa5df +ac363bb542b3daee17bc6ea7516806b54ea15b0beadd7e37f01bcdfe13d7395260af5d0dbc5aaf51a89583a0e0d54a927ea359a87b954adbabb71b3daffd24db +c6c0ca53f9c86201e155bc76ff050000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f7468656d652f5f72 +656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f2451eced0dae2c08 +2e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198720e274a939cd0 +8a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528a2c6cce0239baa +4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c0200001300000000000000000000000000000000005b436f +6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000000000300100005f72 +656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000190200007468656d652f746865 +6d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210007b740aaca0600008f1a00001600000000000000000000000000d60200 +007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000000000000000000000 +00d40900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000cf0a00000000} +{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d +617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 +6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 +656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} +{\*\latentstyles\lsdstimax371\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; +\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; +\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Table;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 1; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Simple 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Classic 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Colorful 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 3; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Columns 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Grid 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 6; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table List 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table 3D effects 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Contemporary;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Elegant;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Professional; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Subtle 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 1;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 2; +\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Web 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Table Theme;\lsdsemihidden1 \lsdlocked0 Placeholder Text; +\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid;\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2; +\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2;\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List; +\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1;\lsdpriority61 \lsdlocked0 Light List Accent 1; +\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdsemihidden1 \lsdlocked0 Revision; +\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1; +\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1;\lsdpriority72 \lsdlocked0 Colorful List Accent 1; +\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; +\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2; +\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2; +\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; +\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3; +\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4; +\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; +\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4; +\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4;\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5; +\lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; +\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; +\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; +\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; +\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; +\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; +\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; +\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; +\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; +\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; +\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; +\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; +\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; +\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; +\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; +\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; +\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; +\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; +\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; +\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; +\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; +\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; +\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;}}{\*\datastore 010500000200000018000000 +4d73786d6c322e534158584d4c5265616465722e362e3000000000000000000000060000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffff0c6ad98892f1d411a65f0040963251e5000000000000000000000000c074 +ec6013add401feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/LICENSE b/LICENSE index a726433..d03a1d9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 2-Clause License -Copyright (c) 2019, Living Computers: Museum+Labs +Copyright Vulcan Inc. 2017-2018 and Living Computer Museum + Labs 2018 All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 245d70e..ece802f 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,558 @@ -# Darkstar -A Xerox Star 8010 Emulator +Readme.txt for Darkstar v1.0: + +1. Introduction and Overview +============================ + +Darkstar provides emulation of the Xerox Dandelion workstation, commonly known +as the Star, 8010, or 1108. + +To avoid confusion in the rest of this document, the name "Star" will be +used to refer to any of the above machines. + +1.1 What's Emulated +------------------- + +Darkstar currently emulates the following Star hardware: + - Standard 8010/1108 Central Processor (CP) with 4KW of microcode store + - i8085-based IO Processor (IOP) + - Up to 1MW of main memory + - Bitmapped Display + - Keyboard / Mouse + - 10, 40, or 80mb hard drives (SA1000 interface) + - 8 inch floppy drive (read-only) + - 10mbit Ethernet + - Real-time clock + +1.2 What's Not +-------------- + +At this time, the below are not emulated by Darkstar: + - Writing to floppy disks + - Sound + - Serial ports + - The LSEP printer interface + +2.0 Requirements +================ + +Darkstar will run on any Windows PC running Windows Vista or later, with version +4.5.3 or later of the .NET Framework installed. .NET should be present by +default on Windows Vista and later; if it is not installed on your computer it +can be obtained at https://www.microsoft.com/net. + +Darkstar will also run under Mono (http://www.mono-project.com/) on Unix +platforms. macOS support will be present in a future release. SDL 2.0 is used +for the emulated display -- use your operating system's package manager to +ensure this is installed. + +The Star keyboard has many keys not present on modern keyboards. Many of +these are mapped to Function keys, arrow keys, and the Home/End/PgUp/PgDown +keys present on most desktop keyboards -- laptop keyboards may be more +difficult to use, depending on your keyboard's layout. + +A three-button mouse is essential for using some Star software (XDE and +Interlisp-D, for example). On most mice, the mousewheel can be clicked to +provide the third (middle) button. Laptops with trackpads may have +configuration options to simulate three buttons but will likely be clumsy to +use. + +If you wish to make use of the emulated Star's Ethernet interface, you +will need to have WinPCAP installed (on Windows) or libpcap (on the Unix of +your choice). WinPCAP can be downloaded from http://www.winpcap.org/. + + +3.0 Getting Started +=================== + +Installation of Darkstar on Windows is simple: Double-click the installer +file, named "DarkstarSetup.msi" and follow the on-screen instructions. The +installer will install all of the necessary files and create two icons on your +Start menu, one for Darkstar itself, and one for its documentation (the file +you're reading now!) + +On Unix platforms, extract the Darkstar-mono.zip archive in a directory of your +choosing. + + +3.1 Using Darkstar +================== + +On Windows, Darkstar can be started by clicking on the "Darkstar" icon created +by the installer. On Unix, Darkstar can be started from a shell prompt by +running "mono Darkstar.exe" in the directory chosen in Section 3.0. + +Once started, the main Darkstar window will appear. This window +is your primary means of interaction with the emulated Star workstation. + + +3.1.1 The Display +----------------- + +The large (initially black) area is the Star's display. Clicking anywhere in +this area while the Star system is running will "capture" the mouse and +keyboard: your mouse movements and keyboard inputs will be sent to the Star, +and mouse movements will be restricted to the Darkstar window. To release the +capture, press either "Alt" key on your keyboard. + + +3.1.2 The Status Bar +-------------------- + +At the bottom of the Darkstar window is the Status Bar. This shows information +about the system. From left to right: + +- The current MP (Maintenance Panel) code. On a real Star, this is a 4 digit + LED display on the front of the CPU unit. The number displayed is used to + communicate boot status and diagnostic information to the user. If the + display reads "----" this indicates that the Star has turned the MP display + off or it has not been initialized. A comprehensive list of MP codes can be + found on Bitsavers at + http://bitsavers.org/pdf/xerox/8010_dandelion/Dandelion_MPCodes_Mar85.pdf. + +- The System status: Stopped or Running. + +- The Emulation speed: In fields per second and as a percentage of a real + Star's execution speed. 78 fields/sec is approximately 100%. + +- Mouse Capture status: Indicates whether the mouse is currently captured. + + +3.1.3 The System Menu +--------------------- + +The System menu allows you to control the Star system and the emulator. The +items in the menu are enumerated below. + +Start / Stop - This will start the Star system running if it is stopped, and + stop it if it is already running. + +Reset - This will reset the Star. This is equivalent to pressing the "B Reset" + button on a real Star. + +Alternate Boot - Allows selection of an alternate boot device. On a real Star, + this is accomplished by holding down the Alt Boot button during + power-up until the appropriate code appears in the MP display. + Selecting a device in this menu will simulate holding the Alt Boot + button as appropriate to select the boot device. + + In general you won't need to change this unless you are installing or + performing maintenance on an operating system. However: Selecting + "Rigid" rather than the default ("DiagnosticRigid") can save time + when booting ViewPoint or XDE. + +Floppy Disk - Allows loading or unloading of floppy disk images. If an image + is currently loaded, its name will be displayed in the space at the + bottom of the submenu; hovering over this space will show the full + path to the image and image metadata, if present. Darkstar uses + floppy disk images in ImageDisk (.IMD) format. + See: http://www.classiccmp.org/dunfield/img/index.htm for details. + +Hard Disk - Allows loading or creating new hard disk images, which typically + have a ".IMG" file extension. If an image is currently loaded, its + name will be displayed in the space at the bottom of the submenu; + hovering over this space will show the full path to the image. + See Section 9.0 for information on the image format. + +Configuration - Invokes the Configuration dialog. See Section 4.0 for more + details on configuration options. + +Show Debugger - Invokes the Debugger interface. See section 5.0 for more + details on care and feeding. + +Exit - Quits Darkstar. Contents of loaded hard disk images are saved to the + image files they were loaded from. + + +3.2 The Keyboard +---------------- + +The Star's keyboard has many keys that are not present on a standard PC +keyboard. Darkstar maps F1-F12, the arrow keys, and the home/end/pgup/pgdown +keys to these special keys, as below: + +Star Key PC Key +---------------------- +Again F1 +Delete F2 +Find F3 +Copy F4 +Same F5 +Move F6 +Open F7 or Left Control +Props F8 or Right Control +Center F9 +Bold F10 +Italics F11 +Underline F12 +Superscript Print Screen +Subscript Scroll Lock +Larger/Smaller Pause +Defaults Num Lock +Skip/Next Home +Undo Page Up +Defn/Expand End +Stop Page Down +Help Up Arrow +Margins Left Arrow +Font Backslash +Keyboard Down + + +3.3 Software +------------ + +3.3.1 Getting Software and Documentation +---------------------------------------- + +Darkstar does not come with any media. Bitsavers has floppy disk sets for +ViewPoint, XDE, and Interlisp-D at: + +http://bitsavers.org/bits/Xerox/8010/ +and +http://bitsavers.org/bits/Xerox/1108/ + +These can be used to bootstrap a fresh installation onto a virtual hard disk. +Note that at this time, only floppy disk images in ImageDisk format (.IMD) are +supported by Darkstar. + +Pre-built hard disk images suitable for use with Darkstar are available at: + +http://bitsavers.org/bits/Xerox/8010/8010_hd_images.zip + + +Documentation for the above operating systems is available at: + +http://bitsavers.org/pdf/xerox/viewpoint +http://bitsavers.org/pdf/xerox/interlisp-d/ +and +http://bitsavers.org/pdf/xerox/xde/ + + +3.3.2 Booting from a Hard Disk +------------------------------ + +If you have an existing hard disk image, you can boot from it by first loading +the image using the "System->Hard Disk->Load..." menu. This will present a +file dialog allowing you to select the image to load. + +After the image is loaded, use the "System->Start" menu to start the Star +running. During boot, the MP code displayed in the lower-left corner of the +window will display various values indicating status, or in the cases of +failure, a diagnostic code. + +ViewPoint and XDE will run a lengthy set of diagnostics during boot -- these +can be skipped by selecting "Rigid" from the "System->Alternate Boot" menu +before starting or restarting the Star. + + +3.3.3 Installing an Operating System +------------------------------------ + +Covering the proper installation and maintenance of the various Star operating +systems is beyond the scope of this manual, but a few poorly documented and +emulator-specific bits of advice are provided here. + +In general, the manuals listed in Section 3.3.1 are the best starting point and +are not too difficult to understand. + +To boot from an OS installation or diagnostic floppy, load the appropriate +floppy disk image using the "System->Floppy Disk->Load..." menu. Then select +the "Floppy" Alternate Boot item from the "System->Alternate Boot" menu and +start or reset the emulated Star system. The system should then boot from the +floppy disk. + +When starting the installation of a new operating system from scratch, there +are a few steps that are not well documented and which are fairly unintuitive: + + 1) In general it is useful to have the time and date set in the Star's TOD + clock before booting. Many Star operating systems and installers + *really* want to know what time it is, and they don't trust you to type + it in. If the TOD has an invalid time / date it will attempt to get it + from the network and in many cases will not proceed until the network + responds. Unless you have an XNS timeserver running on your network + (you probably do not), use the Configuration dialog to set the time + before booting (See section 4.0). + + 2) If you are starting with a new unformatted hard disk the installer will + hang waiting for the disk microcode to read the disk, usually after + printing the initial banner ("Installer Version X.Y of DD-MMM-YY + HH:MM:SS, etc."). It will sit here indefinitely. + + To get past this, you will need to boot the Diagnostic floppy (usually + provided with each set of installation floppies) and use the diagnostics + to format the disk. This is still more complicated than it should be + due to the way the disk microcode interacts with an unformatted disk. + After booting the diagnostic floppy you will be prompted to enter + timezone and time / date information. + + After entering this information, the diagnostic will print something + similar to: + XX Megabyte Storage Diagnostic Program 8.0 of 11-Mar-88 11:16:45 PST + >Fault Analysis + + And then it will pause for 30-45 seconds and fail with: + Fatal error: Microcode. + + After which the system halts and will not respond to input. + + This is because the Fault Analysis step is expecting a formatted disk + and your disk is not yet formatted. The disk microcode is unable to + cope and goes off into the weeds. + + To work around this, when the ">Fault Analysis" line appears during + boot, hit the "Stop" key on the Star's keyboard (this is mapped to + "Page Down.") The diagnostic will print: + key acknowledged + Command stopped + + And leave you at the ">" prompt. Now you can format your disk! + + Or can you? Typing a "?" will give you a list of available commands + but there's nothing related to disk formatting in that list! + + Xerox didn't want the average person to be able to format disks so this + functionality is hidden by default. To enable it, you use the Logon + command -- Type "Logon" and hit return, and it will ask you for a user + name. Use "Xerox" and then provide the password "wizard" (or "elf", + depending on your stature.) Your privileges will be upgraded and now + "?" will reveal a host of fun commands! The "Format" command is what + you want, and is mostly self-explanatory. Do *not* save the bad page + table (as there isn't one, and the microcode will hang trying to read + it.) Formatting will take several minutes after which you will be + asked if you want to recreate the bad page table (say "yes."). You + will given the option to do a media scan (you can if you want, but + emulated disks have no bad spots so there isn't much point.) + + Once the disk has been formatted, you can boot the Installer disk and + go about doing the actual installation. + + 3) Yes, it really does take ViewPoint 10-15 minutes to boot the first time. + It's not particularly swift on subsequent boots, either. Patience is a + virtue when using a Star. + + 4) If you get stuck at MP Code 937 during boot, first try the advice in + (1) above. + Setting dates post-Y2K may cause issues with some operating systems. + On Viewpoint you might also want to install the Set Time utility + (see the official Viewpoint docs and installer help for details). + + 5) The default startup diagnostics that run during Viewpoint or XDE + boot may fail the RTC test (with flashing MP code 0323 / 0007). + This occurs if the emulated Star is not running at 100% speed -- + either because Throttling is off (See Section 4.1) or because your + computer isn't capable of running the emulation at full speed. This is + because the emulated Star is running faster or slower than the test + expects relative to the RTC -- the test thinks that the RTC is behaving + incorrectly. In these cases, you can either (1) Enable Throttling + during boot (if the system is running too fast) or (2) use the + "System->Alternate Boot" menu to select "Rigid" rather than "Default" -- + this will bypass startup diagnostics entirely. + + +4.0 Configuration +================= + +Darkstar's configuration dialog can be invoked with the +"System->Configuration..." menu. This is a small window with multiple tabs. +Each tab is explained in detail in the following subsections. + + +4.1 System Configuration +------------------------ + +The System Configuration tab provides configuration for three options: + +- Memory Size (KW): Configures the amount of memory installed in the system, + From 128KW to 1024KW in 128KW increments. This defaults to 768KW. + Changes made here will not take effect until the system is reset. Note + that memory sizes over 768KW (1.5MB) never officially existed on real Star + hardware and may cause issues with some software that isn't prepared for + it. In particular, Interlisp-D releases later than Harmony will crash + after startup. + +- Host ID (MAC Address): Configures the Star's Ethernet MAC Address (also used + as the system's Host ID for licensing.) If you have multiple instances of + the emulator running on the same network, all instances should have unique + MAC addresses, and you'll also want to make sure that no other real devices + on your network have the same MAC address. Note that changing this on + systems running Viewpoint will likely invalidate any previously entered + product factoring (license) keys. + +- Throttle Execution Speed: Checking this box will limit execution speed to + the execution speed of a real Star. When unchecked, the emulation will run + as fast as the host processor allows. + Note: See Section 3.3.3 for potential pitfalls with this option disabled. + + +4.2 Ethernet Configuration +-------------------------- + +The Ethernet Configuration tab allows the selection of the host network interface +to use with Darkstar. If WinPcap or libpcap is unavailable, no interfaces will +be listed. + + +4.3 Display Configuration +------------------------- + +The Display Configuration tab provides options for the emulated Star's display: + +- Slow Phosphor Simulation: If checked, the slow phosphor of a real Star's + display is simulated. This is not necessary for any real purpose but looks + more authentic and incurs no performance penalty. + +- Display Scale: Allows scaling the display by a factor 1, 2, 3 or 4. This is + useful on 4k (or higher) resolution displays with a high DPI. + + +4.4 Time Configuration +---------------------- + +The Time Configuration tab provides options for initializing the Star's TOD +(time of day) clock at the time the emulation is started or reset. This is +primarily useful to aid in working around the absence of XNS time servers, +the lack of which can cause problems with some Star operating systems. + +There are three options for TOD clock initialization: + +- Current time/date: This sets the Star's TOD to the current time/date with + no adjustments or changes. + +- Current time/date with Y2K compensation: This sets the Star's TOD to the + current time/date with 28 years subtracted from the date. This works + around software that's not Y2K compliant while still allowing the calendar + to match up. + +- Specified time/date: This sets the TOD to a specific time and date. This + is useful for working around Viewpoint product factoring (license) key + expiry. + +- Specified date: This sets the TOD to the specified date, using the + current (real) time. This is useful as above, but allows the Star's + clock to be in sync with reality. + +- No change: This leaves the TOD alone at powerup/reset. Use this if you + plan to set the time manually or via XNS, or if you want to + maintain the current time / date across resets. + + +5.0 Debugger +============ + +Darkstar has an integrated debugger that can make use of microcode and IOP +(8085) source code (if available) to aid in debugging. The debugger can be +invoked via the "System->Show Debugger" menu. + +This debugger is extremely crude, and is not user-friendly at all. + +The debugger consists of three windows -- the Console, the CP Debugger, +and the IOP debugger. Commands can be executed in the Console window, and +sources, disassembly and breakpoints can be viewed in the CP and IOP +debugger windows. + +The "?" or "help" command at the Console window will give you a brief +synopsis of the various commands at your disposal. + + +6.0 Known Issues +================ + +- Interlisp-D releases after Harmony do not run properly. +- The Ethernet hardware has not been thoroughly tested. +- Speed throttling is not implemented on Unix platforms. +- SDL is forced to software-rendering mode due to an odd bug that has yet to + be solved. Performance may suffer as a result. + + +7.0 Reporting Bugs +================== + +If you believe you have found a new issue (or have a feature request) please +send an e-mail to joshd@livingcomputers.org or open an issue on the GitHub +site (see Section 8.0) + +When you send a report, please be as specific and detailed as possible: +- What issue are you seeing? +- What software are you running? +- What operating system are you running Darkstar on? +- What are the exact steps needed to reproduce the issue? + +The more detailed the bug report, the more possible it is for me to track down +the cause. + + +8.0 Source Code +=============== + +The complete source code is available under the BSD license on GitHub at: + +https://github.com/livingcomputermuseum/Darkstar + +Contributions are welcome! + + +9.0 Hard Disk Image Format +========================== + +The Star's hard drive controller is implemented in microcode and controls the +drive at a very low level, so the hard drive image format is not simply a dump +of the sector data on the disk. + +The image consists of a single byte header which indicates the type of SA1000 +disk the image contains data for: + + 1 - Shugart SA1004 (10MB) + 2 - Quantum Q2040 (40MB) + 3 - Quantum Q2080 (80MB) + +All other values are currently invalid. The geometry for the above disks are: + + SA1004 - 256 cylinders, 4 heads (or tracks) + Q2040 - 512 cylinders, 8 heads + Q2080 - 1172 cylinders, 7 heads + +Following the header are multiple 5325 word blocks, one for each track on the +disk, starting at cylinder 0, head 0, followed by cylinder 0, track 1 and so +on. Each word in the disk image is 24 bits wide, written in little-endian +order: The most significant 8 bits indicate the type of data in the word, the +low 16 bits are the data word itself: + + 0 - Disk data or unused + 1 - Address mark (for header, label, or data) + 2 - CRC + +The Star's controller divides each track into 16 sectors; each sector +consists of three fields: Header, Label, and Data. Each of these begins with +an Address Mark - 0x1a141 for the Header, 0x1a143 for the Label and Data. +Each of the fields end with two words of CRC (currently always a dummy value of +0x2beef). + +Xerox specified that the Header is two 16-bit words in length, the Label is +12 words, and the Data field is 256 words. However: As the writing of address +marks, data, and CRC are controlled by microcode (which could potentially vary +between revisions of the operating system) it is probably best not to make +assumptions about the positioning and length of the sectors. If you need to +extract data, parse each track, looking for the address marks and CRCs to +delineate the actual data. + + +10.0 Thanks and Acknowledgements +=============================== + +Darkstar would not have been possible without the amazing preservation work of +Bitsavers.org + +Ethernet encapsulation is provided courtesy of SharpPcap, a WinPcap/LibPcap wrapper. +See: https://github.com/chmorgan/sharppcap. + +Display rendering and keyboard/mouse input is provided through SDL 2.0, see: +https://www.libsdl.org/ and is accessed using the SDL2-CS wrapper, see: +https://github.com/flibitijibibo/SDL2-CS. + + +11.0 Change History +=================== + +V1.0 +---- +Initial release.