1
0
mirror of https://github.com/livingcomputermuseum/DumpDectape.git synced 2026-01-13 15:37:24 +00:00

806 lines
28 KiB
C#

using System;
using System.IO;
using System.IO.Ports;
//
// This program receives an Dectape image in 12, 16, or 18b format from the serial port, from the
// appropriate PDP8 TD8E dump program. It is loosely based on David Gesswein's dumptd8e
// program.
// This program should be running before the PDP8 end is started.
namespace DumpDectape
{
class Program
{
public enum ReadState
{
BlockFlag,
Checksum,
BlockData,
}
static void Main(string[] args)
{
if (!ReadConfiguration(args))
{
// Just exit, configuration is invalid.
return;
}
//
// Open the serial port. Or try, anyway.
//
SerialPort port = new SerialPort(_configuration.PortName, _configuration.BaudRate, _configuration.Parity, 8, _configuration.StopBits);
port.Handshake = Handshake.None;
port.Open();
string tapeFileName = _configuration.FileName;
string diagFileName = Path.Combine(Path.GetDirectoryName(tapeFileName), Path.GetFileNameWithoutExtension(tapeFileName) + ".log");
// Open the output file
using (FileStream tapeOutput = new FileStream(tapeFileName, FileMode.Create, FileAccess.Write),
diagOutput = new FileStream(diagFileName, FileMode.Create, FileAccess.Write))
{
IBlockProcessor blockProcessor = null;
int tapeBits = 0;
switch (_configuration.TapeType)
{
case TapeType.Twelve:
blockProcessor = new BlockProcessor12(tapeOutput);
tapeBits = 12;
break;
case TapeType.Sixteen:
blockProcessor = new BlockProcessor16(tapeOutput);
tapeBits = 16;
break;
case TapeType.Eighteen:
blockProcessor = new BlockProcessor18(tapeOutput);
tapeBits = 18;
break;
default:
throw new InvalidOperationException("Unexpected tape type.");
}
StreamWriter diagText = new StreamWriter(diagOutput);
diagText.AutoFlush = true;
diagText.WriteLine("Summary log for {0}-bit DECTape image {1} captured on {2}:", tapeBits, tapeFileName, DateTime.Now);
Console.WriteLine("Initialized, waiting for tape data.");
ReadState readState = ReadState.BlockFlag;
int blockNumber = 0;
int checksumByteIndex = 0;
ushort temp = 0;
int chksum12 = 0;
int badBlocks = 0;
while (true)
{
byte[] buf = new byte[200];
int read = port.Read(buf, 0, buf.Length);
for (int i = 0; i < read; i++)
{
ulong b = buf[i];
switch (readState)
{
case ReadState.BlockFlag:
switch (b)
{
case 0xff:
case 0xfd:
blockProcessor.StartBlock();
readState = ReadState.BlockData;
if (b == 0xfd)
{
Console.Write(" {0} bad ", blockNumber);
diagText.WriteLine("Block {0} bad.", blockNumber);
badBlocks++;
}
break;
case 0xfe:
readState = ReadState.Checksum;
checksumByteIndex = 0;
break;
default:
diagText.WriteLine("Error during transmission.");
throw new InvalidOperationException("Missing start of block flag. Aborting.");
}
break;
case ReadState.Checksum:
// read first byte of checksum
if (checksumByteIndex == 0)
{
temp = (ushort)b;
checksumByteIndex = 1;
}
else
{
// last byte of checksum
temp = (ushort)(temp | (b << 8));
chksum12 = (temp + chksum12) & 0xfff;
if (chksum12 != 0)
{
Console.WriteLine("Warning: Checksum error during read: {0}.", ToOctal(chksum12));
diagText.WriteLine("Warning: Checksum error during read: {0}.", ToOctal(chksum12));
}
Console.WriteLine("Read completed {0}. {1} blocks read, {2} bad blocks.", chksum12 == 0 ? "successfully" : "with checksum errors", blockNumber, badBlocks);
diagText.WriteLine("Read completed {0}. {1} blocks read, {2} bad blocks.", chksum12 == 0 ? "successfully" : "with checksum errors", blockNumber, badBlocks);
return;
}
break;
case ReadState.BlockData:
//
// Process the current byte.
//
blockProcessor.ProcessByte(b);
//
// Deal with the 12-bit checksum.
//
switch (checksumByteIndex++)
{
case 0:
temp = (ushort)b;
break;
case 1:
temp = (ushort)((temp | (b << 8)) & 0xfff);
chksum12 = chksum12 + temp;
temp = (ushort)(b >> 4);
break;
case 2:
temp = (ushort)((temp | (b << 4)) & 0xfff);
chksum12 = chksum12 + temp;
checksumByteIndex = 0;
break;
default:
throw new InvalidOperationException("Unexpected state when building 12-bit checksum.");
}
//
// Start next block when at end.
//
if (blockProcessor.BlockDone)
{
readState = ReadState.BlockFlag;
blockNumber++;
checksumByteIndex = 0;
if ((blockNumber % 5) == 0)
{
Console.Write("{0}", blockNumber);
}
else
{
Console.Write(".");
}
}
break;
}
}
}
}
}
public static ulong MangleBits(ulong mangled)
{
ulong unmangled;
//
// Reading 18-bit or 16-bit DECTapes on a 12-bit PDP-8 presents an interesting
// ordering of 6-bit half-words.
// Effectively, each 36-bit sequence reorders 6-bit sequences as:
// original: 5 4 3 2 1 0
// mangled: 2 5 4 1 0 3
// This routine moves things back to the right place in the 36-bit word.
//
unmangled =
((mangled & 0x00000003f) << 6) |
((mangled & 0x000000fc0) << 6) |
((mangled & 0x00003f000) << 18) |
((mangled & 0x000fc0000) >> 18) |
((mangled & 0x03f000000) >> 6) |
((mangled & 0xfc0000000) >> 6);
return unmangled;
}
public static string ToOctal(int i)
{
return Convert.ToString(i, 8);
}
private static void PrintUsage()
{
Console.WriteLine("DumpDectape captures SIMH-compatible Dectape images from a PDP-8 system running the appropriate dump program.");
Console.WriteLine("usage: DumpDectape -port <port> [-baud <baud>] [-stop <stop>] [-parity <parity>] -type <type> -file <filename>");
Console.WriteLine(" <port> specifies the serial port to use (e.g. 'com1')");
Console.WriteLine(" <baud> specifies the baud rate to use (e.g. '9600')");
Console.WriteLine(" <stop> specifies the number of stop bits (e.g. '1')");
Console.WriteLine(" <parity> specifies the parity (e.g. 'Even')");
Console.WriteLine(" If unspecified, DumpDectape defaults to 9600 baud, 1 stop bit, no parity.");
Console.WriteLine(" <type> specifies the type of Dectape being dumped: 18b, 16b, or 12b.");
Console.WriteLine(" <filename> specifies the name of the SIMH image file to create.");
}
private static bool ReadConfiguration(string[] args)
{
_configuration = new Configuration();
int index = 0;
while(index < args.Length)
{
try
{
switch (args[index++])
{
case "-port":
_configuration.PortName = args[index++];
break;
case "-baud":
_configuration.BaudRate = int.Parse(args[index++]);
break;
case "-stop":
switch(args[index++])
{
case "1":
_configuration.StopBits = StopBits.One;
break;
case "2":
_configuration.StopBits = StopBits.Two;
break;
case "1.5":
_configuration.StopBits = StopBits.OnePointFive;
break;
default:
throw new InvalidOperationException("Invalid Stop Bits specification.");
}
break;
case "-parity":
_configuration.Parity = (Parity)Enum.Parse(typeof(Parity), args[index++]);
break;
case "-type":
switch(args[index++])
{
case "12b":
_configuration.TapeType = TapeType.Twelve;
break;
case "16b":
_configuration.TapeType = TapeType.Sixteen;
break;
case "18b":
_configuration.TapeType = TapeType.Eighteen;
break;
default:
throw new InvalidOperationException("Invalid Tape Type specification.");
}
break;
case "-file":
_configuration.FileName = args[index++];
break;
default:
throw new InvalidOperationException("Invalid option.");
}
}
catch
{
// TODO: be more helpful about where the user screwed up.
PrintUsage();
return false;
}
}
//
// Ensure required options have been set, if not, print usage instructions.
if (_configuration.TapeType == TapeType.Unspecified ||
string.IsNullOrWhiteSpace(_configuration.PortName) ||
string.IsNullOrWhiteSpace(_configuration.FileName))
{
PrintUsage();
return false;
}
else
{
return true;
}
}
private enum TapeType
{
Unspecified = 0,
Twelve,
Sixteen,
Eighteen,
}
private class Configuration
{
public Configuration()
{
// Set things to defaults.
FileName = null;
PortName = null;
BaudRate = 9600;
Parity = Parity.None;
StopBits = StopBits.One;
TapeType = TapeType.Unspecified;
}
public string FileName;
public string PortName;
public int BaudRate;
public Parity Parity;
public StopBits StopBits;
public TapeType TapeType;
}
private static Configuration _configuration;
}
public interface IBlockProcessor
{
void StartBlock();
void ProcessByte(ulong b);
bool BlockDone { get; }
}
public class BlockProcessor18 : IBlockProcessor
{
public BlockProcessor18(Stream outputStream)
{
_outStream = outputStream;
_byteIndex36 = 0;
_thirtySix = 0;
_wordCount18 = 0;
_blockDone = true;
}
public bool BlockDone
{
get { return _blockDone; }
}
public void StartBlock()
{
_blockDone = false;
_byteIndex36 = 0;
_wordCount18 = 0;
_thirtySix = 0;
}
/// <summary>
/// Appends a new byte onto the output data.
/// </summary>
/// <param name="b"></param>
/// <returns>true if the end of the block has been reached, false otherwise</returns>
public void ProcessByte(ulong b)
{
if (_blockDone)
{
throw new InvalidOperationException("Invalid state: ProcessByte called before StartBlock.");
}
//
// For 18-bit tapes:
// There are 384 12-bit words per block and each 12-bit word is
// transferred as 1.5 bytes. The 384 12-bit words in a block
// correspond to 256 18-bit words (1.5 12-bit words per 18-bit word)
// which we want to write out to an 18-bit SIMH dectape image. This
// is further complicated by the fact that due to the way the
// bits actually get written to the tape, reading 18-bit words
// as 1.5 12-bit words ends up transposing 6-bit segments inside
// a 36-bit segment:
// If the original 6-bit half-word ordering is:
// 5 4 3 2 1 0
// When read back into sequential 12-bit words, these half-words get
// rearranged:
// 2 5 4 1 0 3
//
// To keep things somewhat easy to understand here (so as to preserve
// my own sanity) at the expense of slightly longer code,
// the state machine below parses 9 bytes (72 bits) at a time
// which corresponds to 2 36-bit words (4 18-bit words) and re-arranges the 6-bit
// half-words after each 36-bit word is read in. These re-arranged words are then
// pushed out to disk in standard SIMH 18b format.
//
switch (_byteIndex36++)
{
case 0:
_thirtySix = b;
break;
case 1:
_thirtySix |= (ulong)(b << 8);
break;
case 2:
_thirtySix |= (ulong)(b << 16);
break;
case 3:
_thirtySix |= (ulong)(b << 24);
break;
case 4:
_thirtySix |= (ulong)((b & 0xf) << 32);
// First 36-bit word finished.
WriteThirtySix(_outStream, Program.MangleBits(_thirtySix));
_wordCount18 += 2;
// Start next 36-bit word
_thirtySix = (ulong)((b & 0xf0) >> 4);
break;
case 5:
_thirtySix |= (ulong)(b << 4);
break;
case 6:
_thirtySix |= (ulong)(b << 12);
break;
case 7:
_thirtySix |= (ulong)(b << 20);
break;
case 8:
_thirtySix |= (ulong)(b << 28);
// Second 36-bit word finished.
WriteThirtySix(_outStream, Program.MangleBits(_thirtySix));
_wordCount18 += 2;
_byteIndex36 = 0;
break;
default:
throw new InvalidOperationException("Unexpected state when building 36-bit word.");
}
if (_wordCount18 == 256)
{
// Finished with the current block, reset our word count
// and let the caller know we're done.
_blockDone = true;
}
}
private static void WriteThirtySix(Stream outStream, ulong thirtySix)
{
WriteEighteen(outStream, (uint)(thirtySix & 0x3ffff));
WriteEighteen(outStream, (uint)((thirtySix & 0xffffc0000) >> 18));
}
private static void WriteEighteen(Stream outStream, uint eighteen)
{
outStream.WriteByte((byte)(eighteen & 0x000000ff));
outStream.WriteByte((byte)((eighteen & 0x0000ff00) >> 8));
outStream.WriteByte((byte)((eighteen & 0x00ff0000) >> 16));
outStream.WriteByte((byte)((eighteen & 0xff000000) >> 24));
outStream.Flush();
}
/// <summary>
/// The current byte index into the two 36-bit words we're building
/// </summary>
private int _byteIndex36;
/// <summary>
/// The current 36-bit word we've built
/// </summary>
private ulong _thirtySix;
/// <summary>
/// The number of 18-bit words we've read.
/// </summary>
private int _wordCount18;
/// <summary>
/// Whether we've completed a block (and StartBlock must be called before processing more data)
/// </summary>
private bool _blockDone;
/// <summary>
/// The stream we'll flush the data to.
/// </summary>
private Stream _outStream;
}
public class BlockProcessor16 : IBlockProcessor
{
public BlockProcessor16(Stream outputStream)
{
_outStream = outputStream;
_byteIndex36 = 0;
_thirtySix = 0;
_wordCount16 = 0;
_blockDone = true;
}
public bool BlockDone
{
get { return _blockDone; }
}
public void StartBlock()
{
_blockDone = false;
_byteIndex36 = 0;
_wordCount16 = 0;
_thirtySix = 0;
}
/// <summary>
/// Appends a new byte onto the output data.
/// </summary>
/// <param name="b"></param>
/// <returns>true if the end of the block has been reached, false otherwise</returns>
public void ProcessByte(ulong b)
{
if (_blockDone)
{
throw new InvalidOperationException("Invalid state: ProcessByte called before StartBlock.");
}
//
// For 16-bit tapes:
// See the comments in BlockProcessor18.ProcessByte for the nitty-gritty.
// 16-bit tapes are handled nearly identically, since the format on tape is identical.
// The difference is that 16-bit dectapes do not use the upper 2 bits of each
// 18-bit word on tape. After the typical 18-bit processing and re-arrangement, we write out the data
// in the standard 16-bit SIMH format.
// Some of this code could technically be factored out.
//
switch (_byteIndex36++)
{
case 0:
_thirtySix = b;
break;
case 1:
_thirtySix |= (ulong)(b << 8);
break;
case 2:
_thirtySix |= (ulong)(b << 16);
break;
case 3:
_thirtySix |= (ulong)(b << 24);
break;
case 4:
_thirtySix |= (ulong)((b & 0xf) << 32);
// First 36-bit word finished.
WriteThirtySix(_outStream, Program.MangleBits(_thirtySix));
_wordCount16 += 2;
// Start next 36-bit word
_thirtySix = (ulong)((b & 0xf0) >> 4);
break;
case 5:
_thirtySix |= (ulong)(b << 4);
break;
case 6:
_thirtySix |= (ulong)(b << 12);
break;
case 7:
_thirtySix |= (ulong)(b << 20);
break;
case 8:
_thirtySix |= (ulong)(b << 28);
// Second 36-bit word finished.
WriteThirtySix(_outStream, Program.MangleBits(_thirtySix));
_wordCount16 += 2;
_byteIndex36 = 0;
break;
default:
throw new InvalidOperationException("Unexpected state when building 36-bit word.");
}
if (_wordCount16 == 256)
{
// Finished with the current block, reset our word count
// and let the caller know we're done.
_blockDone = true;
}
}
private static void WriteThirtySix(Stream outStream, ulong thirtySix)
{
WriteSixteen(outStream, (uint)(thirtySix & 0xffff));
// TODO: verify this is correct.
WriteSixteen(outStream, (uint)((thirtySix & 0x3fffc0000) >> 18));
}
private static void WriteSixteen(Stream outStream, uint sixteen)
{
outStream.WriteByte((byte) (sixteen & 0x00ff));
outStream.WriteByte((byte)((sixteen & 0xff00) >> 8));
outStream.Flush();
}
/// <summary>
/// The current byte index into the two 36-bit words we're building
/// </summary>
private int _byteIndex36;
/// <summary>
/// The current 36-bit word we've built
/// </summary>
private ulong _thirtySix;
/// <summary>
/// The number of 18-bit words we've read.
/// </summary>
private int _wordCount16;
/// <summary>
/// Whether we've completed a block (and StartBlock must be called before processing more data)
/// </summary>
private bool _blockDone;
/// <summary>
/// The stream we'll flush the data to.
/// </summary>
private Stream _outStream;
}
public class BlockProcessor12 : IBlockProcessor
{
public BlockProcessor12(Stream outputStream)
{
_outStream = outputStream;
_byteIndex12 = 0;
_twelve = 0;
_wordCount12 = 0;
_blockDone = true;
}
public bool BlockDone
{
get { return _blockDone; }
}
public void StartBlock()
{
_blockDone = false;
_byteIndex12 = 0;
_twelve = 0;
_wordCount12 = 0;
}
/// <summary>
/// Appends a new byte onto the output data.
/// </summary>
/// <param name="b"></param>
/// <returns>true if the end of the block has been reached, false otherwise</returns>
public void ProcessByte(ulong b)
{
if (_blockDone)
{
throw new InvalidOperationException("Invalid state: ProcessByte called before StartBlock.");
}
//
// For 12-bit tapes:
// There are 129 12-bit words per block and each 12-bit word is
// transferred as 1.5 bytes. The reassembled 12-bit words are then
// pushed out to disk in standard SIMH 12b format.
//
switch (_byteIndex12++)
{
case 0:
_twelve = b;
break;
case 1:
_twelve |= (ulong)(b << 8);
// First 12-bit word finished.
WriteTwelve(_outStream, _twelve);
_wordCount12++;
// Start next 12-bit word.
_twelve = (ulong)(b >> 4);
break;
case 2:
// Second 12-bit word finished.
_twelve |= (ulong)((b << 4));
WriteTwelve(_outStream, _twelve);
_wordCount12++;
// Start over.
_byteIndex12 = 0;
break;
default:
throw new InvalidOperationException("Unexpected state when building 12-bit word.");
}
if (_wordCount12 == 129)
{
// Finished with the current block.
_blockDone = true;
}
}
private static void WriteTwelve(Stream outStream, ulong twelve)
{
outStream.WriteByte((byte) (twelve & 0x0ff));
outStream.WriteByte((byte)((twelve & 0xf00) >> 8));
outStream.Flush();
}
/// <summary>
/// The current byte index into the two 36-bit words we're building
/// </summary>
private int _byteIndex12;
/// <summary>
/// The current 36-bit word we've built
/// </summary>
private ulong _twelve;
/// <summary>
/// The number of 18-bit words we've read.
/// </summary>
private int _wordCount12;
/// <summary>
/// Whether we've completed a block (and StartBlock must be called before processing more data)
/// </summary>
private bool _blockDone;
/// <summary>
/// The stream we'll flush the data to.
/// </summary>
private Stream _outStream;
}
}