swh:1:snp:63e2d142f91fc04ec33789d9d7bb85f3bef72e05
Raw File
Tip revision: 66d8e606a8d996ded60bc81d5edf319142a5fad9 authored by Ron Burkey on 04 October 2021, 11:49:55 UTC
Merge branch 'master' of https://github.com/virtualagc/virtualagc
Tip revision: 66d8e60
yaOBC.c
/*
 * Copyright 2011,2020 Ronald S. Burkey <info@sandroid.org>
 *
 * This file is part of yaAGC.
 *
 * yaAGC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * yaAGC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with yaAGC; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Filename:    yaOBC.c
 * Purpose:     Emulator for Gemini OBC CPU
 * Compiler:    GNU gcc.
 * Reference:   http://www.ibibio.org/apollo
 * Mods:        2011-12-23 RSB  By all that's good and holy, I
 *                              should be adapting this from
 *                              yaAGC or yaAGS ... but I'm not.
 *                              It's easier (or at least, less
 *                              horrible) to start from scratch
 *                              and accept the consequences than it
 *                              is to try and figure out old code.
 *           	2019-09-18 RSB	Removed references to LVDC.  Back
 *           			then, the OBC and LVDC seemed
 *           			very similar.  Now they seem only
 *           			*somewhat* similar, with enough
 *           			maddening differences that I don't
 *           			feel like cramming them both into
 *           			the same simulator.  And it looks
 *           			like I never really implemented any
 *           			of it anyway.
 *           	2020-12-06 RSB  Fixed a clang warning (PRF_TBD vs
 *           	                PRO_TBD).
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <math.h>
#include <pthread.h>
#include "enet/enet.h"
#ifdef WIN32
#include <windows.h>
struct tms
  {
    clock_t tms_utime; /* user time */
    clock_t tms_stime; /* system time */
    clock_t tms_cutime; /* user time of children */
    clock_t tms_cstime; /* system time of children */
  };
#define _SC_CLK_TCK (1000)
#define sysconf(x) (x)
#define times(p) (clock_t)GetTickCount ()
#else
#include <sys/times.h>
#endif
#include "../yaASM/yaASM.h"

//==========================================================================
// Some function prototypes for forward-references.

int
ParseCommandLine(int argc, char *argv[]);
int
ReadBinaryFile(void);
int
WriteBinaryFile(char *BinaryFile);
int
ReadSymbolFile(void);
int
WriteIoFile(char *IoFile);
int
ReadIoFile(void);
void
PrintAddress(Address_t *Address);
void
SetCyclesPerTick(double NewSpeedup);
long
ComputeNeededCycles(void);
void
RunOneMachineCycle(void);
void *
DebuggerThreadFunction(void *Data);
void *
HalSockThreadFunction(void *Data);
void
DisplayCurrentDebuggingLocation(void);
void
BuildAddressFromObcHopRegister(uint32_t RawHopRegister, Address_t *HopRegister);
void
BuildObcHopRegisterFromAddress(Address_t *HopRegister, uint32_t *RawHop);
void
DisplayDebuggerPrompt(void);
int
CompareSourceByAddresses(const void *s1, const void *s2);
int
CompareSymbols(const void *s1, const void *s2);
int
ParseLocation(char *Location, uint32_t **Mem32, uint16_t **Mem16M,
    uint16_t **Mem16L, Address_t **MemAddress);
int
ParseValue(char *ValueString, int *Value);
void
SleepMilliseconds(unsigned Milliseconds);

typedef int
HalOutputFunction_t(int yx, int32_t Value);
typedef int
HalInputFunction_t(int yx, int32_t *Value);
HalOutputFunction_t ProOutputFunctionMem, CldOutputFunctionMem,
    ProOutputFunctionSock, CldOutputFunctionSock;
HalInputFunction_t ProInputFunctionMem, CldInputFunctionMem,
    ProInputFunctionSock, CldInputFunctionSock;
int
HalSockInitialize(void);
void
HalSockBroadcastString(char *String, int Length);

//==========================================================================
// Constants, type definitions, global variables ....

#define BITS13 017777
#define BITS26 0377777777
#define BIT13 010000
#define BIT26 0200000000
#define BREAK13 0x8000
#define BREAK26 0x80000000

// Temporary variables.
static Line_t LineBuffer;

// Command-line arguments.
#define PORT 19653
int Run = 0, Port = PORT, Verbosity = 0;
char *BinaryFile = "yaOBC.bin";
char *SymbolFile = NULL;
char *IoFile = "yaOBC.io";

// Machine state: HOP register, memory image.  Note that we use a structural
// representation of the HOP register and convert back/forth to a pure
// integer form when needed (which is only when a system snapshot is read
// or written).  The Accumulator we always keep as a simple integer.
// The PQ register we treat in the following manner: we always immediately
// load the lower 26 bits with the result it's supposed to hold as the
// result of an MPY or DIV, but since those results aren't actually
// supposed to be available immediately, we use bits D28-D30 as a
// countdown timer that increments once per machine cycle; so the contents
// of PQ are valid only when the upper nibble reaches 0.  Bit D31 is used
// as a watchpoint flag for both 26-bit registers.
BinaryImage_t Binary;
Address_t HopRegister;
uint32_t Accumulator, PqRegister;

// The symbol table and source code for symbolic debugging.
SymbolList_t Symbols;
int NumSymbols = 0;
typedef struct
{
  Address_t Address;
  uint32_t Value;
  Line_t SourceText;
} SourceLine_t;
SourceLine_t *SourceLines = NULL;
int MaxSourceLines = 0;
int NumSourceLines = 0;

// Emulated program timing.
int StepN = 0;
unsigned long TotalCycles = 0;
double Speedup = 0; // Just 0 for setup purposes.
double CyclesPerTick;
#define SECONDS_PER_CYCLE 0.000140
// The CurrentXXXX values relate just to the interval since the
// emulated program last started freely running ... i.e., if the
// program was ever paused for single-stepping, breakpoints,
// etc., these values are reset.  Only the TotalCycles accounts
// for execution prior to the last pause.
long CurrentStartCycles = 0, CurrentCycles = 0;
clock_t CurrentStartTicks = 0, CurrentTicks = 0;
struct tms TmsStruct;

// For the socket interface.
pthread_t HalSockThread;
pthread_mutex_t HalSockMutex =
PTHREAD_MUTEX_INITIALIZER;
typedef struct
{
  int Type; // 0 for PRO, 1 for CLD
  int yx;
  unsigned long Data;
  unsigned long Count;
  unsigned long Order;
} HalSockEvent_t;
#define MAX_HALSOCK_EVENTS 2048
HalSockEvent_t HalSockEvents[MAX_HALSOCK_EVENTS];
int NumHalSockEvents = 0;

int
HalSockEventCmp(const void *e1, const void *e2)
{
#define EVENT1 ((HalSockEvent_t *) e1)
#define EVENT2 ((HalSockEvent_t *) e2)
  int i;
  i = EVENT1->Count - EVENT2->Count;
  if (i == 0)
    i = EVENT1->Order - EVENT2->Order;
  return (i);
#undef EVENT1
#undef EVENT2
}

// For the debugger thread.
pthread_t DebuggerThread;
pthread_mutex_t DebuggerMutex =
PTHREAD_MUTEX_INITIALIZER;
Line_t DebuggerUserInput;
volatile int NeedDebuggerPrompt = 1;
int DebuggerPause = 0; // The debugger sets this to get emulation to stop.
int DebuggerQuit = 0; // The debugger sets this to request a shutdown.
int DebuggerRun = 0; // The debugger sets this to request the emulation to start.
int EmulatorEndOfSector = 0; // The emulator sets this after reaching end-of-sector.
int EmulatorPause = 0; // The emulator sets this to pause emulation.
int EmulatorTimeout = 0; // Emulator sets this if PRO/CLD instruction times out.
int EmulatorUnimplementedYX = 0; // Unimplemented YX for PRO/CLD/SHF.
int EmulatorBreakpoint = 0;
int EmulatorWatchpoint = 0;
int IgnoreBreakpoint = 0;
enum WatchmodeType_t
{
  WT_ANY, WT_WRITE, WT_CHANGE
};
enum WatchmodeType_t WatchmodeType = WT_CHANGE;
// The next variable is the HOP the last time the debugger displayed
// the source code and machine status.  It's used to keep the same display
// from appearing over and over.
Address_t LastDebuggedHopRegister =
  { 0xFFFF };
const char *ObcOps[] =
  { "HOP", "DIV", "PRO", "RSU", "ADD", "SUB", "CLA", "AND", "MPY", "TRA",
      "SHF", "TMI", "STO", "SPQ", "CLD", "TNZ" };

// For helping to determine what to do with PRO instructions, as to whether
// input or output is done.
enum ProType_t
{
  PRO_ILLEGAL = 0,
  PRO_TBD,
  PRO_OUTPUT,
  PRO_INPUT,
  PRO_TRS_PULSES,
  PRO_REENT_ATM
};
enum PeripheralType_t
{
  PRF_ILLEGAL = 0,
  PRF_TBD,
  PRF_ACME,
  PRF_AGE,
  PRF_ATM,
  PRF_DCS,
  PRF_FDI,
  PRF_IMU,
  PRF_IS,
  PRF_IVI,
  PRF_MDIU,
  PRF_PCDP,
  PRF_RR,
  PRF_TRS,
  PRF_DCS_RR,
  PRF_TRS_ATM
};
typedef struct
{
  enum ProType_t Direction;
  enum PeripheralType_t Peripheral;
  HalInputFunction_t *Input;
  HalOutputFunction_t *Output;
  int32_t Value; // Used only by the MEM driver.
} ProCategory_t;
ProCategory_t ProCategories[64] =
  {
    { PRO_INPUT, PRF_DCS_RR }, // YX = 00
        { PRO_OUTPUT, PRF_DCS_RR }, // YX = 01
        { PRO_OUTPUT, PRF_FDI }, // YX = 02
        { PRO_OUTPUT, PRF_FDI }, // YX = 03
        { PRO_OUTPUT, PRF_FDI }, // YX = 04
        { PRO_OUTPUT, PRF_PCDP }, // YX = 05
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 06
        { PRO_OUTPUT, PRF_FDI }, // YX = 07
        { PRO_OUTPUT, PRF_IS }, // YX = 10
        { PRO_OUTPUT, PRF_IVI }, // YX = 11
        { PRO_OUTPUT, PRF_IVI }, // YX = 12
        { PRO_OUTPUT, PRF_IVI }, // YX = 13
        { PRO_OUTPUT, PRF_TRS_ATM }, // YX = 14
        { PRO_REENT_ATM, PRF_TBD }, // YX = 15
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 16
        { PRO_OUTPUT, PRF_FDI }, // YX = 17
        { PRO_TRS_PULSES, PRF_TRS }, // YX = 20
        { PRO_OUTPUT, PRF_TRS }, // YX = 21
        { PRO_OUTPUT, PRF_AGE }, // YX = 22
        { PRO_OUTPUT, PRF_AGE }, // YX = 23
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 24
        { PRO_OUTPUT, PRF_TRS_ATM }, // YX = 25
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 26
        { PRO_OUTPUT, PRF_FDI }, // YX = 27
        { PRO_OUTPUT, PRF_MDIU }, // YX = 30
        { PRO_OUTPUT, PRF_MDIU }, // YX = 31
        { PRO_OUTPUT, PRF_MDIU }, // YX = 32
        { PRO_OUTPUT, PRF_MDIU }, // YX = 33
        { PRO_OUTPUT, PRF_PCDP }, // YX = 34
        { PRO_OUTPUT, PRF_IVI }, // YX = 35
        { PRO_INPUT, PRF_IMU }, // YX = 36
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 37
        { PRO_OUTPUT, PRF_MDIU }, // YX = 40
        { PRO_OUTPUT, PRF_MDIU }, // YX = 41
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 42
        { PRO_INPUT, PRF_MDIU }, // YX = 43
        { PRO_OUTPUT, PRF_ATM }, // YX = 44
        { PRO_INPUT, PRF_IMU }, // YX = 45
        { PRO_INPUT, PRF_IMU }, // YX = 46
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 47
        { PRO_OUTPUT, PRF_MDIU }, // YX = 50
        { PRO_OUTPUT, PRF_MDIU }, // YX = 51
        { PRO_OUTPUT, PRF_MDIU }, // YX = 52
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 53
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 54
        { PRO_TBD, PRF_TBD }, // YX = 55
        { PRO_INPUT, PRF_IMU }, // YX = 56
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 57
        { PRO_OUTPUT, PRF_MDIU }, // YX = 60
        { PRO_TBD, PRF_TBD }, // YX = 61
        { PRO_INPUT, PRF_PCDP }, // YX = 62
        { PRO_OUTPUT, PRF_RR }, // YX = 63
        { PRO_TBD, PRF_TBD }, // YX = 64
        { PRO_OUTPUT, PRF_TRS }, // YX = 65
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 66
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 67
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 70
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 71
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 72
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 73
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 74
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 75
        { PRO_ILLEGAL, PRF_ILLEGAL }, // YX = 76
        { PRO_ILLEGAL, PRF_ILLEGAL } // YX = 77
  };
typedef struct
{
  enum PeripheralType_t Peripheral;
  HalInputFunction_t *Input;
  HalOutputFunction_t *Output;
  int32_t Value; // Used only by the MEM driver.
} CldCategory_t;
CldCategory_t CldCategories[64] =
  {
    { PRF_RR }, // YX = 00
        { PRF_MDIU }, // YX = 01
        { PRF_MDIU }, // YX = 02
        { PRF_MDIU }, // YX = 03
        { PRF_MDIU }, // YX = 04
        { PRF_TRS }, // YX = 05
        { PRF_ILLEGAL }, // YX = 06
        { PRF_ILLEGAL }, // YX = 07
        { PRF_PCDP }, // YX = 10
        { PRF_PCDP }, // YX = 11
        { PRF_IS }, // YX = 12
        { PRF_PCDP }, // YX = 13
        { PRF_ATM }, // YX = 14
        { PRF_ATM }, // YX = 15
        { PRF_PCDP }, // YX = 16
        { PRF_PCDP }, // YX = 17
        { PRF_ILLEGAL }, // YX = 20
        { PRF_PCDP }, // YX = 21
        { PRF_IVI }, // YX = 22
        { PRF_ILLEGAL }, // YX = 23
        { PRF_TBD }, // YX = 24
        { PRF_IVI }, // YX = 25
        { PRF_IVI }, // YX = 26
        { PRF_AGE }, // YX = 27
        { PRF_TBD }, // YX = 30
        { PRF_IVI }, // YX = 31
        { PRF_AGE }, // YX = 32
        { PRF_ATM }, // YX = 33
        { PRF_ATM }, // YX = 34
        { PRF_ATM }, // YX = 35
        { PRF_TBD }, // YX = 36
        { PRF_ILLEGAL }, // YX = 37
        { PRF_ILLEGAL }, // YX = 40
        { PRF_ATM }, // YX = 41
        { PRF_TBD }, // YX = 42
        { PRF_ATM }, // YX = 43
        { PRF_ATM }, // YX = 44
        { PRF_ILLEGAL }, // YX = 45
        { PRF_ILLEGAL }, // YX = 46
        { PRF_ILLEGAL }, // YX = 47
        { PRF_ILLEGAL }, // YX = 50
        { PRF_ILLEGAL }, // YX = 51
        { PRF_ILLEGAL }, // YX = 52
        { PRF_ILLEGAL }, // YX = 53
        { PRF_ILLEGAL }, // YX = 54
        { PRF_ILLEGAL }, // YX = 55
        { PRF_ILLEGAL }, // YX = 56
        { PRF_ILLEGAL }, // YX = 57
        { PRF_ILLEGAL }, // YX = 60
        { PRF_ILLEGAL }, // YX = 61
        { PRF_ILLEGAL }, // YX = 62
        { PRF_ILLEGAL }, // YX = 63
        { PRF_ILLEGAL }, // YX = 64
        { PRF_ILLEGAL }, // YX = 65
        { PRF_ILLEGAL }, // YX = 66
        { PRF_ILLEGAL }, // YX = 67
        { PRF_ILLEGAL }, // YX = 70
        { PRF_ILLEGAL }, // YX = 71
        { PRF_ILLEGAL }, // YX = 72
        { PRF_ILLEGAL }, // YX = 73
        { PRF_ILLEGAL }, // YX = 74
        { PRF_ILLEGAL }, // YX = 75
        { PRF_ILLEGAL }, // YX = 76
        { PRF_ILLEGAL } // YX = 77
  };

//==========================================================================

int
main(int argc, char *argv[])
{
  int i, RetVal = 1, WasRunning = 0;

#ifdef PTW32_STATIC_LIB
  // You wouldn't need this if I had compiled pthreads_w32 as a DLL.
  pthread_win32_process_attach_np();
#endif

  printf("yaOBC emulator for Gemini OBC computer.\n");
  printf("Built " __DATE__ ", " __TIME__ "\n");

  // Various setups.
  for (i = 0; i < 64; i++)
    {
      ProCategories[i].Input = ProInputFunctionSock;
      CldCategories[i].Input = CldInputFunctionSock;
      ProCategories[i].Output = ProOutputFunctionSock;
      CldCategories[i].Output = CldOutputFunctionSock;
    }
  if (ParseCommandLine(argc, argv))
    goto Done; // Error!
  if (ReadBinaryFile())
    goto Done; // Error!
  if (SymbolFile != NULL && ReadSymbolFile())
    goto Done; // Error!
  if (0 != (i = ReadIoFile()))
    {
      if (i != 1)
        {
          printf("Read-error on file %s.\n", IoFile);
          goto Done;
          // Error!
        }
      printf("Warning: Could not read %s, zeroing PRO/CLD MEM driver.\n",
          IoFile);
      for (i = 0; i < MAX_YX; i++)
        {
          ProCategories[i].Value = 0;
          CldCategories[i].Value = 0;
        }
    }
  SetCyclesPerTick(1.0); // Emulation speedup vs. real-time is 1X.
  CurrentStartTicks = times(&TmsStruct);
  DisplayCurrentDebuggingLocation();
  if (0 != (i = pthread_create(&DebuggerThread, NULL, DebuggerThreadFunction,
      NULL)))
    {
      printf("Could not create debugger thread (error %d).\n", i);
      goto Done;
    }
  if (HalSockInitialize())
    {
      printf("Could not start the socket system.\n");
      goto Done;
    }

  // Emulate.
  while (1)
    {
      // Set NeedDebuggerPrompt non-zero whenever we detect a condition
      // by which the emulator (rather than the debugger) halts program
      // execution.  The debugger itself will be stuck waiting for
      // user input, and won't know to print a new prompt.
      long CyclesNeeded = 0;

      if (WasRunning && Run == 0)
        {
          char Input[41] = "R 0.0";
          HalSockBroadcastString(Input, strlen(Input));
          sprintf(Input, "S %lu", TotalCycles);
          HalSockBroadcastString(Input, strlen(Input));
        }
      WasRunning = Run;

      // Take care of any queued PRO/CLD related events from peripheral
      // emulations.  If any queued incoming events are ready to fire,
      // timewise, we write their values to the PRO/CLD memory arrays
      // and remove the events from the queue.  The removal is an inefficient
      // operation that could doubtless be made much better by some
      // mechanism such as trees in place of the qsort/memmove I'm actually
      // using right now.
      if (NumHalSockEvents > 0 && HalSockEvents[0].Count <= TotalCycles)
        {
          pthread_mutex_lock(&HalSockMutex);
          for (i = 0; i < NumHalSockEvents && HalSockEvents[i].Count
              <= TotalCycles; i++)
            {
              if (HalSockEvents[i].Type == 0)
                ProCategories[HalSockEvents[i].yx].Value
                    = HalSockEvents[i].Data;
              else
                CldCategories[HalSockEvents[i].yx].Value
                    = HalSockEvents[i].Data ? BITS26 : 0;
            }
          if (i < NumHalSockEvents)
            memmove(&HalSockEvents[0], &HalSockEvents[i],
                (NumHalSockEvents - i) * sizeof(HalSockEvent_t));
          NumHalSockEvents -= i;
          pthread_mutex_unlock(&HalSockMutex);
        }

      CurrentTicks = times(&TmsStruct); // Get current real time in ticks.

      // Figure out how many machine cycles we want to run.  There
      // are two cases:  Either we're free-running (Run!=0), in which
      // case we want just as many cycles as will catch up to real time,
      // or else we're not free-running (Run==0).  In the latter case,
      // It's still possible that the debugging interface may have
      // commanded that we execute a certain number of cycles.
      pthread_mutex_lock(&DebuggerMutex);
      if (EmulatorEndOfSector)
        {
          printf(
              "\nProgram execution reached end of sector, emulation paused.\n");
          EmulatorEndOfSector = 0;
          Run = 0;
          NeedDebuggerPrompt = 1;
        }
      if (EmulatorTimeout)
        {
          printf("\nPRO or CLD instruction timed out.\n");
          EmulatorTimeout = 0;
          Run = 0;
          NeedDebuggerPrompt = 1;
        }
      if (EmulatorBreakpoint)
        {
          printf("\nBreakpoint on code reached.\n");
          EmulatorBreakpoint = 0;
          Run = 0;
          NeedDebuggerPrompt = 1;
        }
      if (EmulatorWatchpoint)
        {
          printf("\nBreakpoint on data reached.\n");
          EmulatorWatchpoint = 0;
          Run = 0;
          NeedDebuggerPrompt = 1;
        }
      if (EmulatorUnimplementedYX)
        {
          printf("\nPRO/CLD/SHF instruction used unimplemented YX.\n");
          EmulatorUnimplementedYX = 0;
          Run = 0;
          NeedDebuggerPrompt = 1;
        }
      if (DebuggerQuit)
        break;
      if (DebuggerPause || EmulatorPause)
        {
          Run = 0;
          DebuggerPause = 0;
          EmulatorPause = 0;
          NeedDebuggerPrompt = 1;
        }
      if (DebuggerRun)
        {
          char Input[41];
          sprintf(Input, "R %lf", Speedup);
          HalSockBroadcastString(Input, strlen(Input));
          CurrentStartCycles = CurrentCycles = 0;
          CurrentStartTicks = CurrentTicks;
          Run = 1;
          DebuggerRun = 0;
          NeedDebuggerPrompt = 1;
        }
      if (StepN)
        {
          CyclesNeeded = StepN;
          StepN = 0;
          NeedDebuggerPrompt = 0;
        }
      else if (Run)
        CyclesNeeded = ComputeNeededCycles();
      if ((!Run && !CyclesNeeded) || NeedDebuggerPrompt)
        {
          DisplayCurrentDebuggingLocation();
          NeedDebuggerPrompt = 0;
        }
      pthread_mutex_unlock(&DebuggerMutex);

      // Emulate like the wind!
      for (; CyclesNeeded && !DebuggerPause && !EmulatorPause
          && !EmulatorEndOfSector && !EmulatorBreakpoint && !EmulatorWatchpoint; CyclesNeeded--)
        RunOneMachineCycle();

      // Sleep for a little to avoid hogging 100% CPU time.  The amount
      // we choose doesn't really matter.
      SleepMilliseconds(10);
    }

  RetVal = 0;
  Done: ;

#ifdef PTW32_STATIC_LIB
  // You wouldn't need this if I had compiled pthreads_w32 as a DLL.
  pthread_win32_process_detach_np();
#endif

  return (RetVal);
}

//==========================================================================

// Parse the command line into the corresponding global variables, returning
// 0 on success or non-zero on failure.
int
ParseCommandLine(int argc, char *argv[])
{
  int i, j, RetVal = 1;

  // Parse the command-line arguments.
  for (i = 1; i < argc; i++)
    {
      if (!strcmp(argv[i], "--help"))
        {
          Help: ;
          printf("USAGE:\n"
            "\tyaOBC [OPTIONS]\n"
            "The allowed OPTIONS are:\n"
            "--help        Display this usage info and then exit.\n"
            "--binary=F    Specifies name of file containing memory/ATM\n"
            "              contents and HOP constant. Defaults to\n"
            "              yaOBC.bin, which is normally produced by\n"
            "              yaOBC itself, but can be a binary file made\n"
            "              by yaASM also.\n"
            "--symbols=F   Specifies the name of the listing file\n"
            "              created by yaASM. Used to get the symbol\n"
            "              table and source code for symbolic debugging.\n"
            "              If absent, debugging will be non-symbolic.\n"
            "--run         Start the emulator in a mode in which it is\n"
            "              running the selected OBC binary in real\n"
            "              time.  The default is to start in paused mode,\n"
            "              immediately prior to running the first \n"
            "              instruction.\n"
            "--port=P      Specifies the TCP/IP port on which to listen\n"
            "              for connections to peripheral-emulating programs\n"
            "              like yaPanel.  Defaults to 19653.\n"
            "-v            Increase verbosity level of messages.\n"
            "--method=P,T  This switch relates to the method by which\n"
            "              emulated or physical peripheral devices\n"
            "              connect to the emulated CPU. In essence, it\n"
            "              allows different types of device drivers to\n"
            "              be used for different YX ranges for the PRO\n"
            "              and CLD instructions of the CPU.  The P field\n"
            "              selects the YX range by the designator of a\n"
            "              specific peripheral device.  The choices for P\n"
            "              are ALL (meaning, all peripherals), DCS,\n"
            "              RR, TRS, MDIU, IVI, IMU, FDI, ACME, AGE,\n"
            "              IS, ATM, PCDP.  The T field specifies the\n"
            "              the data-transport method for the YX ranges\n"
            "              associated with peripheral P, and the choices\n"
            "              for it are: MEM, SOCK (the default), COM1,\n"
            "              COM2, ..., CUSTOM.  The MEM choice would typically\n"
            "              be used when merely debugging the OBC software\n"
            "              without peripherals.  The SOCK choice would \n"
            "              be used for emulated peripherals provided directly\n"
            "              by the Virtual AGC project.  The COMn choice\n"
            "              could be used for building a physical peripheral\n"
            "              like an MDIU or IVI.  CUSTOM would relate to\n"
            "              a compiled-in driver of a presently unknown type,\n"
            "              such as a driver for the Orbiter spacecraft-\n"
            "              simulation system.\n"
            "--io=F        Specifies a file containing initial PRO/CLD\n"
            "              values for the MEM/SOCK driver of the --method\n"
            "              switch. The default is --io=yaOBC.io, or simply\n"
            "              all zeroes if that file does not exist.\n"
            "--com1=P      Used only with --method type RS232.  P is the\n"
            "--com2=P      name of the desired comport, such as COM1 or\n"
            "etc.          /dev/ttyS0.\n");
          goto Done;
        }
      else if (!strcmp(argv[i], "-v"))
        Verbosity++;
      else if (!strcmp(argv[i], "--run"))
        Run = 1;
      else if (1 == sscanf(argv[i], "--port=%d", &j))
        {
          Port = j;
          if (Port < 0 || Port > 0xFFFF)
            {
              printf("Illegal TCP/IP port.\n");
              goto Done;
            }
        }
      else if (!strncmp(argv[i], "--binary=", 9))
        BinaryFile = &argv[i][9];
      else if (!strncmp(argv[i], "--symbols=", 10))
        SymbolFile = &argv[i][10];
      else if (!strncmp(argv[i], "--method=", 9))
        {
          printf(
              "Sorry, --method is not yet implemented. MEM method always used.\n");
          goto Done;
        }
      else if (!strncmp(argv[i], "--com", 5))
        {
          printf("Sorry, --comN is not yet implemented.\n");
          goto Done;
        }
      else
        {
          printf("Unrecognised switch: %s\n", argv[i]);
          goto Help;
        }
    }

  // And print out info about the settings.
  if (Verbosity)
    {
      printf("Emulation: %s\n", "Gemini OBC");
      if (Run)
        printf("State: running in real time.\n");
      else
        printf("State: paused prior to first instruction.\n");
      printf("Binary file: %s\n", BinaryFile);
      if (SymbolFile == NULL)
        printf("No symbol table.\n");
      else
        printf("Symbol-table file: %s\n", SymbolFile);
      printf("TCP/IP port: %d\n", Port);
      printf("\n");
    }

  RetVal = 0;
  Done: ;
  return (RetVal);
}

//==========================================================================

// Read the OBC executable binary specified by the global command-line
// variables into RAM, returning 0 on success or non-zero on failure.
int
ReadBinaryFile(void)
{
  int RetVal = 1;
  FILE *fp = NULL;
  uint32_t RawHopRegister;

  if (Verbosity)
    printf("Reading binary file \"%s\" ...\n", BinaryFile);

  fp = fopen(BinaryFile, "rb");
  if (fp == NULL)
    {
      printf("Selected binary file \"%s\" not found.\n", BinaryFile);
      goto Done;
    }

  if (1 != fread(&Binary, sizeof(Binary), 1, fp) || 1 != fread(&RawHopRegister,
      sizeof(RawHopRegister), 1, fp) || 1 != fread(&Accumulator,
      sizeof(Accumulator), 1, fp) || 1 != fread(&PqRegister,
      sizeof(PqRegister), 1, fp))
    {
      printf("Unexpected end of binary file.\n");
      goto Done;
    }
  BuildAddressFromObcHopRegister(RawHopRegister, &HopRegister);

  if (Verbosity)
    {
      printf("Finished reading binary file.\n");
      printf("Program entry point: ");
      PrintAddress(&HopRegister);
      printf("\n");
    }

  RetVal = 0;
  Done: ;
  if (fp != NULL)
    fclose(fp);
  return (RetVal);
}

//==========================================================================

// Write a OBC executable binary representing the current OBC
// state, returning 0 on success or non-zero on failure.
int
WriteBinaryFile(char *BinaryFile)
{
  int RetVal = 1;
  FILE *fp = NULL;
  uint32_t RawHopRegister;

  if (Verbosity)
    printf("Writing snapshot file \"%s\" ...\n", BinaryFile);

  fp = fopen(BinaryFile, "wb");
  if (fp == NULL)
    {
      printf("Cannot create snapshot file \"%s\".\n", BinaryFile);
      goto Done;
    }

  BuildObcHopRegisterFromAddress(&HopRegister, &RawHopRegister);

  if (1 != fwrite(&Binary, sizeof(Binary), 1, fp) || 1 != fwrite(
      &RawHopRegister, sizeof(RawHopRegister), 1, fp) || 1 != fwrite(
      &Accumulator, sizeof(Accumulator), 1, fp) || 1 != fwrite(&PqRegister,
      sizeof(PqRegister), 1, fp))
    {
      printf("Write-error on snapshot.\n");
      goto Done;
    }

  if (Verbosity)
    printf("Finished writing snapshot file.\n");

  RetVal = 0;
  Done: ;
  if (fp != NULL)
    fclose(fp);
  return (RetVal);
}

//==========================================================================

// Read the OBC assembly-listing specified by the global command-line
// variables into RAM, returning 0 on success or non-zero on failure.
// The listing contains two things of interest to us:
//   1. The source code.  Every line of source code that's of consequence
//      to use has two things of interest: The address, which is the very
//      first thing on the line, and the source, which is everything after
//      the first tab-character to the end of the line.
//   2. The symbol table.
// We read these items of interest into memory structures for future
// reference, and discard the rest.
int
ReadSymbolFile(void)
{
  int i, j, RetVal = 1, m, p, s, w, v;
  char c, *Tab, *Colon;
  FILE *fp;

  fp = fopen(SymbolFile, "r");
  if (fp == NULL)
    {
      printf("Selected listing file \"%s\" not found.\n", SymbolFile);
      goto Done;
    }

  if (Verbosity)
    printf("Reading source code from listing-file \"%s\" ...\n", SymbolFile);

  // Read in the file up to the symbol table.
  while (NULL != fgets(LineBuffer, sizeof(LineBuffer), fp))
    {
      int LineFields;
      // End of code and start of symbol table?
      if (!strncmp("SYMBOL TABLE", LineBuffer, 12))
        break;
      // A line of code is useful to us only if it starts with an
      // address and has a tab character in it..
      if (!isspace(LineBuffer[0]) && 5 <= (LineFields = sscanf(LineBuffer,
          "%o-%o-%o-%o%c%o", &m, &p, &s, &w, &c, &v)) && c == ' ' && m >= 0
          && m < MAX_MODULES && p >= 0 && p < MAX_SECTORS && s >= 0 && s
          < MAX_SYLLABLES && w >= 0 && w < MAX_WORDS && NULL != (Tab = strstr(
          LineBuffer, "\t")))
        {
          // Enough space to store in the source-code table?
          if (NumSourceLines >= MaxSourceLines)
            {
              if (MaxSourceLines == 0)
                MaxSourceLines = 100000;
              else
                MaxSourceLines += 5000;
              SourceLines = realloc(SourceLines, MaxSourceLines
                  * sizeof(SourceLine_t));
              if (SourceLines == NULL)
                {
                  printf("Out of memory for the symbol table.\n");
                  goto Done;
                }
            }
          Tab[strcspn(Tab, "\r\n")] = 0; // Trim trailing EOL chars.
          strcpy(SourceLines[NumSourceLines].SourceText, Tab + 1);
          // We don't actually care about the assembler's idea
          // of half-word mode any longer.
          SourceLines[NumSourceLines].Address.HalfWordMode = 0;
          SourceLines[NumSourceLines].Address.Module = m;
          SourceLines[NumSourceLines].Address.Page = p;
          SourceLines[NumSourceLines].Address.Syllable = s;
          SourceLines[NumSourceLines].Address.Word = w;
          if (LineFields > 5)
            SourceLines[NumSourceLines].Value = v;
          else
            SourceLines[NumSourceLines].Value = ILLEGAL_VALUE;
          NumSourceLines++;
        }
    }
  // Sort by address, so we can find them again later easily.
  qsort(SourceLines, NumSourceLines, sizeof(SourceLine_t),
      CompareSourceByAddresses);
  if (Verbosity)
    printf("%d lines of source code found.\n", NumSourceLines);

  if (Verbosity)
    printf("Reading symbols from listing-file \"%s\" ...\n", SymbolFile);

  // Read in the symbol table.
  while (NULL != fgets(LineBuffer, sizeof(LineBuffer), fp))
    {
      char SymbolName[9], RefName[9], PseudoName[9];
      // End of symbol table and start of octal listing?
      if (!strncmp("OCTAL LISTING", LineBuffer, 13))
        break;
      // It's a symbol definition only if it starts with an
      // address and contains a colon.
      if (NULL == (Colon = strstr(LineBuffer, ":")) || Colon != &LineBuffer[20])
        continue;
      *Colon = 0;
      if (!isspace(LineBuffer[0]) && 5 == sscanf(LineBuffer, "%o-%o-%o-%o,%s",
          &m, &p, &s, &w, SymbolName) && m >= 0 && m < MAX_MODULES && p >= 0
          && p < MAX_SECTORS && s >= 0 && s < MAX_SYLLABLES && w >= 0 && w
          < MAX_WORDS)
        {
          int IsRef = 0;
          // Enough space to store in the source-code table?
          if (NumSymbols >= MAXSYMBOLS)
            {
              printf("Too many symbols.\n");
              goto Done;
            }
          // Set up the address, symbol name, and default value.
          Symbols[NumSymbols].Address.HalfWordMode = 0;
          Symbols[NumSymbols].Address.Module = m;
          Symbols[NumSymbols].Address.Page = p;
          Symbols[NumSymbols].Address.Syllable = s;
          Symbols[NumSymbols].Address.Word = w;
          strcpy(Symbols[NumSymbols].Name, SymbolName);
          Symbols[NumSymbols].Line = 0;
          Symbols[NumSymbols].RefName[0] = 0;
          Symbols[NumSymbols].RefType = ST_NONE;
          Symbols[NumSymbols].Type = ST_NONE;
          Symbols[NumSymbols].Value = ILLEGAL_VALUE;
          // Now deduce some of the other fields:
          Colon += 2;
          if (3 == sscanf(Colon, "Constant (%o), created via \"%s %[^\"]\"",
              &j, PseudoName, RefName))
            {
              Symbols[NumSymbols].Type = ST_CONSTANT;
              Symbols[NumSymbols].Value = j;
              IsRef = 1;
            }
          else if (1 == sscanf(Colon, "Constant (%o)", &j))
            {
              Symbols[NumSymbols].Type = ST_CONSTANT;
              Symbols[NumSymbols].Value = j;
            }
          else if (2 == sscanf(Colon,
              "Uninitialized variable, created via \"%s %[^\"]\"", PseudoName,
              RefName))
            {
              Symbols[NumSymbols].Type = ST_VARIABLE;
              IsRef = 1;
            }
          else if (!strncmp(Colon, "Uninitialized variable", 22))
            {
              Symbols[NumSymbols].Type = ST_VARIABLE;
            }
          else if (2 == sscanf(Colon,
              "Left-hand symbol, created via \"%s %[^\"]\"", PseudoName,
              RefName))
            {
              Symbols[NumSymbols].Type = ST_CODE;
              IsRef = 1;
            }
          else if (!strncmp(Colon, "Left-hand symbol", 16))
            {
              Symbols[NumSymbols].Type = ST_CODE;
            }
          if (IsRef)
            {
              strcpy(Symbols[NumSymbols].RefName, RefName);
              if (!strcmp(PseudoName, "HOPC"))
                Symbols[NumSymbols].RefType = ST_HOPC;
              else if (!strcmp(PseudoName, "SYN"))
                Symbols[NumSymbols].RefType = ST_SYN;
              else if (!strcmp(PseudoName, "EQU"))
                Symbols[NumSymbols].RefType = ST_EQU;
            }
          // Next, please!
          NumSymbols++;
        }
    }
  qsort(Symbols, NumSymbols, sizeof(Symbol_t), CompareSymbols);
  if (Verbosity)
    printf("%d symbols found.\n", NumSymbols);

  if (Verbosity > 5)
    {
      printf("\n");
      printf("Source code\n");
      printf("-----------\n");
      for (i = 0; i < NumSourceLines; i++)
        {
          PrintAddress(&SourceLines[i].Address);
          printf("\t%s\n", SourceLines[i].SourceText);
        }
      printf("\n");
      printf("Symbol table\n");
      printf("------------\n");
      for (i = 0; i < NumSymbols; i++)
        {
          PrintAddress(&Symbols[i].Address);
          printf(", %-8s: ", Symbols[i].Name);
          switch (Symbols[i].Type)
            {
          case ST_CODE:
            printf("Left-hand symbol");
            break;
          case ST_VARIABLE:
            printf("Uninitialized variable");
            break;
          case ST_CONSTANT:
            printf("Constant (%09o)", Symbols[i].Value);
            break;
          default:
            printf("Implementation error");
            break;
            }
          switch (Symbols[i].RefType)
            {
          case ST_NONE:
            break;
          case ST_HOPC:
            printf(", created via \"HOPC %s\"", Symbols[i].RefName);
            break;
          case ST_SYN:
            printf(", created via \"SYN %s\"", Symbols[i].RefName);
            break;
          case ST_EQU:
            printf(", created via \"EQU %s\"", Symbols[i].RefName);
            break;
          default:
            printf(", implementation error");
            break;
            }
          printf("\n");
        }
    }
  if (Verbosity)
    printf("\n");

  fclose(fp);

  RetVal = 0;
  Done: ;
  return (RetVal);
}

//==========================================================================

void
PrintAddress(Address_t *Address)
{
  printf("%o-%02o-%o-%03o", Address->Module, Address->Page, Address->Syllable,
      Address->Word);
}

//==========================================================================

// Used for computing the global variable CyclesPerTick when the desired
// execution rate of the simulated program with respect to real time
// changes.  Speedup is 1.0 for real-time, 0.5 for half-speed, 2.0
// for double-speed, and so on.
void
SetCyclesPerTick(double NewSpeedup)
{
  if (NewSpeedup != Speedup)
    {
      Speedup = NewSpeedup;
      CyclesPerTick = Speedup / sysconf(_SC_CLK_TCK) / SECONDS_PER_CYCLE;
      CurrentStartTicks = CurrentTicks;
      CurrentStartCycles = CurrentCycles;
    }
}

//==========================================================================

long
ComputeNeededCycles(void)
{
  return (lround(CyclesPerTick * (CurrentTicks - CurrentStartTicks)
      - CurrentCycles));
}

//==========================================================================

// Display register values, source code, etc., at the current debugging
// point, along with a debugger prompt.
void
DisplayCurrentDebuggingLocation(void)
{
  uint32_t RawHopRegister;
  int SourceLineFound = 0, Value = ILLEGAL_VALUE;
  Address_t Address;

  if (Run || !memcmp(&HopRegister, &LastDebuggedHopRegister, sizeof(Address_t)))
    return;
  memcpy(&LastDebuggedHopRegister, &HopRegister, sizeof(Address_t));

  // Display registers.
  Value = Binary[0][(HopRegister.Word & 0400) ? RESIDUAL_SECTOR
        : HopRegister.Page][HopRegister.Syllable][HopRegister.Word & 0377];
  BuildObcHopRegisterFromAddress(&HopRegister, &RawHopRegister);
  printf("\n");
  printf("HOP=%09o (", RawHopRegister);
  printf("ADR=");
  PrintAddress(&HopRegister);
  printf(" HWM=%d VAL=%05o", HopRegister.HalfWordMode, Value & BITS13);
  printf(")\n");
  printf("ACC=%09o PQ=%09o (TMR:%d)\n", Accumulator & BITS26, (PqRegister
      & BITS26), ((PqRegister >> 28) & 0x07));

  // Display timing info.
  printf("Cycles=%ld (%.5lf seconds)\n", TotalCycles, TotalCycles
      * SECONDS_PER_CYCLE);

  // Display source line ... from the assembler's listing file if we
  // can, disassembled if not.
  memcpy(&Address, &HopRegister, sizeof(Address_t));
  if (SourceLines != NULL)
    {
      SourceLine_t Key, *Result = NULL;
      // Search the list of source lines for our current address according
      // to the HOP register.  For OBC this is a little tricky, because
      // we can't know what program modules are loaded, so we search through
      // all program modules looking for one with the same memory contents
      // that we actually have.  Another thing that makes it tricky is SYN,
      // since that may give different source lines for the same address,
      // but without the proper Value field.
      int i;
      memcpy(&Key.Address, &HopRegister, sizeof(Address_t));
      if (Key.Address.Word & 0400)
	{
	  Key.Address.Page = RESIDUAL_SECTOR;
	  Key.Address.Word &= 0377;
	}
      for (i = 0; i < MAX_MODULES; i++)
	{
	  Key.Address.Module = i;
	  Result = bsearch(&Key, SourceLines, NumSourceLines,
	      sizeof(SourceLine_t), CompareSourceByAddresses);
	  if (Result == NULL)
	    continue;
	  // If there are multiple lines associated with the same
	  // address, this might not be the first one.  Need to
	  // search backward linearly to be sure.
	  for (; Result > SourceLines && !CompareSourceByAddresses(&Key,
	      Result - 1); Result--)
	    ;
	  // Now iterate to try to fine a match with respect
	  // to the contents of the memory location.
	  Retry: ;
	  if (Result->Value == (Value & BITS13))
	    {
	      SourceLineFound = 1;
	      break;
	    }
	  else
	    {
	      Result++;
	      if (Result < &SourceLines[NumSourceLines]
		  && !CompareSourceByAddresses(&Key, Result))
		goto Retry;
	    }
	}
      if (SourceLineFound)
        {
          Address.Module = Key.Address.Module;
          PrintAddress(&Address);
          printf("\t");
          printf("%s\n", Result->SourceText);
        }
    }
  if (!SourceLineFound)
    {
      PrintAddress(&Address);
      printf("\t");
      if (Value == ILLEGAL_VALUE)
        printf("\t (Uninitialized memory)\n");
      printf("\t %s  %03o\n", ObcOps[(Value >> 9) & 0x0F], Value & 0777);
    }

  DisplayDebuggerPrompt();

}

//==========================================================================

void
BuildAddressFromObcHopRegister(uint32_t RawHopRegister, Address_t *HopRegister)
{
  HopRegister->Module = 0;
  HopRegister->Word = RawHopRegister & 0777;
  HopRegister->Page = (RawHopRegister >> 9) & 017;
  HopRegister->Syllable = (RawHopRegister >> 14) & 03;
  HopRegister->HalfWordMode = (RawHopRegister >> 17) & 01;
}

//==========================================================================

void
BuildObcHopRegisterFromAddress(Address_t *HopRegister, uint32_t *RawHopRegister)
{
  (*RawHopRegister) = 0;
  (*RawHopRegister) |= (HopRegister->Word & 0777);
  (*RawHopRegister) |= (HopRegister->Page & 017) << 9;
  (*RawHopRegister) |= (HopRegister->Syllable & 03) << 14;
  (*RawHopRegister) |= (HopRegister->HalfWordMode & 01) << 17;
}

//==========================================================================

void
DisplayDebuggerPrompt(void)
{
  printf("%s debugger %s> ", "OBC",
      (Run || DebuggerRun) ? "(running)" : "(paused)");
  fflush(stdout);
}

//==========================================================================

// Compare two SourceLine_t by address, for use with qsort() or bsearch().
int
CompareSourceByAddresses(const void *s1, const void *s2)
{
#define Sym1 ((SourceLine_t *) s1)
#define Sym2 ((SourceLine_t *) s2)
  int i;
  i = Sym1->Address.Module - Sym2->Address.Module;
  if (i)
    return (i);
  i = Sym1->Address.Page - Sym2->Address.Page;
  if (i)
    return (i);
  i = Sym1->Address.Syllable - Sym2->Address.Syllable;
  if (i)
    return (i);
  i = Sym1->Address.Word - Sym2->Address.Word;
  return (i);
#undef Sym1
#undef Sym2
}

//==========================================================================

// Compares two Symbol_t structures on the basis of symbol
// name, for use with qsort() or bsearch().
int
CompareSymbols(const void *s1, const void *s2)
{
#define Sym1 ((Symbol_t *) s1)
#define Sym2 ((Symbol_t *) s2)
  return (strcmp(Sym1->Name, Sym2->Name));
#undef Sym1
#undef Sym2
}

//==========================================================================

// Hardware-abstraction: method types for the --method command-line switch.
// All functions return:
//      0 on success
//      1 on errors such as bad YX
//      2 on timeout
// Note that while the CPU doesn't support anything for discrete outputs,
// we nevertheless provide a function for it, for use by the debugger.

// These particular HAL functions are for type=MEM.

int
ProOutputFunctionMem(int yx, int32_t Value)
{
  if (yx < 0 || yx >= MAX_YX)
    return (1);
  ProCategories[yx].Value = Value & BITS26;
  return (0);
}

int
ProInputFunctionMem(int yx, int32_t *Value)
{
  if (yx < 0 || yx >= MAX_YX)
    return (1);
  *Value = (ProCategories[yx].Value & BITS26);
  return (0);
}

int
CldOutputFunctionMem(int yx, int32_t Value)
{
  if (yx < 0 || yx >= MAX_YX)
    return (1);
  CldCategories[yx].Value = (Value != 0);
  return (0);
}

int
CldInputFunctionMem(int yx, int32_t *Value)
{
  if (yx < 0 || yx >= MAX_YX)
    return (1);
  *Value = (CldCategories[yx].Value != 0);
  return (0);
}

//==========================================================================

// Read an io file into ProCategories[] and CldCategories[].  Returns:
//      0 success
//      1 file not found
//      2 error
int
ReadIoFile(void)
{
  int RetVal = 2, i, j, k;
  FILE *fp = NULL;

  fp = fopen(IoFile, "r");
  if (fp == NULL)
    {
      RetVal = 1; // file not found.
      goto Done;
    }

  for (i = 0; i < MAX_YX; i++)
    {
      if (2 != fscanf(fp, "PRO %o %o\n", &k, &j) || k != i)
        goto Done;
      ProCategories[i].Value = j & BITS26;
    }
  for (i = 0; i < MAX_YX; i++)
    {
      if (2 != fscanf(fp, "CLD %o %o\n", &k, &j) || k != i)
        goto Done;
      CldCategories[i].Value = j & BITS26;
    }

  RetVal = 0;
  Done: ;
  if (fp != NULL)
    fclose(fp);
  return (RetVal);
}

//==========================================================================

// Write an io file sourced from ProCategories[] and CldCategories[].  Returns:
//      0 success
//      2 error
int
WriteIoFile(char *IoFile)
{
  int RetVal = 2, i;
  FILE *fp = NULL;

  fp = fopen(IoFile, "w");
  if (fp == NULL)
    goto Done;

  for (i = 0; i < MAX_YX; i++)
    {
      if (fprintf(fp, "PRO %02o %09o\n", i, ProCategories[i].Value & BITS26)
          <= 0)
        goto Done;
    }
  for (i = 0; i < MAX_YX; i++)
    {
      if (fprintf(fp, "CLD %02o %o\n", i, CldCategories[i].Value != 0) <= 0)
        goto Done;
    }

  RetVal = 0;
  Done: ;
  if (fp != NULL)
    fclose(fp);
  return (RetVal);
}

//==========================================================================

// This is really the CPU emulator here.  It executes a single OBC machine
// cycle.
void
RunOneMachineCycle(void)
{
#define DATA_ANY_BREAK() \
  if (!IgnoreBreakpoint && WatchmodeType == WT_ANY) \
    if ((*dMem0 != ILLEGAL_VALUE && (*dMem0 & BREAK13) != 0) || \
        (dMem1 != NULL && *dMem1 != ILLEGAL_VALUE && (*dMem1 & BREAK13) != 0)) \
      { \
        EmulatorWatchpoint = 1; \
        return; \
      }
#define PQ_WRITE_BREAK() \
  if (!IgnoreBreakpoint && (PqRegister & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define PQ_ANY_BREAK() \
  if (!IgnoreBreakpoint && WatchmodeType == WT_ANY && (PqRegister & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define ACC_WRITE_BREAK() \
  if (!IgnoreBreakpoint && (Accumulator & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define ACC_ANY_BREAK() \
  if (!IgnoreBreakpoint && WatchmodeType == WT_ANY && (Accumulator & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define CLD_ANY_BREAK() \
  if (!IgnoreBreakpoint && WatchmodeType == WT_ANY && (CldCategories[yx].Value & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define PRO_ANY_BREAK() \
  if (!IgnoreBreakpoint && WatchmodeType == WT_ANY && (ProCategories[yx].Value & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }
#define PRO_WRITE_BREAK() \
  if (!IgnoreBreakpoint && (ProCategories[yx].Value & BREAK26) != 0) \
    { \
      EmulatorWatchpoint = 1; \
      return; \
    }

  int WasJump = 0, Value, Instruction, hwm = 0, Data, ValueToStore;
  uint16_t /* *pMem, */ *dMem0, *dMem1;
  Address_t JumpHOP;

  // Fetch the current value pointed to by the HOP register.
  hwm = HopRegister.HalfWordMode;
  Value = Binary[0][(HopRegister.Word & 0400) ? RESIDUAL_SECTOR
      : HopRegister.Page][HopRegister.Syllable][HopRegister.Word & 0377];

  // Get the instruction we want to decode.
  if (Value == ILLEGAL_VALUE)
    {
      printf("Trying to execute from ininitialized memory at ");
      PrintAddress(&HopRegister);
      printf("\n");
      EmulatorPause = 1;
      return;
    }
  Instruction = (Value >> 9) & 017;
  if (!IgnoreBreakpoint && 0 != (Value & BREAK13))
    {
      EmulatorBreakpoint = 1;
      return;
    }
  Value &= 0777;
  // pMem points to the operand memory location as if it contains code.
  /*
  pMem
      = (Value & 0400) ? (&Binary[0][RESIDUAL_SECTOR][HopRegister.Syllable][Value
          & 0377])
          : (&Binary[0][HopRegister.Page][HopRegister.Syllable][Value & 0377]);
  */
  // dMem0 and dMem1 point to the operand memory location as if it contains data.
  if (hwm)
    {
      dMem0 = (Value & 0400) ? (&Binary[0][RESIDUAL_SECTOR][2][Value & 0377])
          : (&Binary[0][HopRegister.Page][2][Value & 0377]);
      dMem1 = NULL;
      Data = *dMem0 & BITS13;
      if (Data & BIT13)
        Data |= ~BITS13; // Sign extend.
    }
  else
    {
      dMem0 = (Value & 0400) ? (&Binary[0][RESIDUAL_SECTOR][0][Value & 0377])
          : (&Binary[0][HopRegister.Page][0][Value & 0377]);
      dMem1 = (Value & 0400) ? (&Binary[0][RESIDUAL_SECTOR][1][Value & 0377])
          : (&Binary[0][HopRegister.Page][1][Value & 0377]);
      Data = (*dMem0 & BITS13) | ((*dMem1 & BITS13) << 13);
      if (Data & BIT26)
        Data |= ~BITS26; // Sign extend.
    }

  // Decode the instruction.
  switch (Instruction)
    {
  case 00: // HOP
    DATA_ANY_BREAK()
    ;
    WasJump = 1;
    BuildAddressFromObcHopRegister(Data, &JumpHOP);
    break;
  case 01: // DIV
    {
      int Dividend, Divisor, Quotient;
      DATA_ANY_BREAK();
      ACC_ANY_BREAK();
      PQ_WRITE_BREAK();
      Dividend = Accumulator & BITS26;
      Divisor = Data;
      if (Divisor == 0)
        Quotient = 0;
      else
        Quotient = Dividend / Divisor;
      PqRegister = (PqRegister & ~BITS26) | (Quotient & BITS26);
      PqRegister |= 0x50000000;
    }
    break;
  case 02: // PRO
    {
      int ClearAcc, yx;
      int32_t InputValue;
      ClearAcc = Value & 0400;
      yx = Value & 077;
      switch (ProCategories[yx].Direction)
        {
      case PRO_ILLEGAL:
        EmulatorUnimplementedYX = 1;
        break;
      case PRO_TBD:
        EmulatorUnimplementedYX = 1;
        break;
      case PRO_OUTPUT:
        ACC_ANY_BREAK()
        ;
        PRO_WRITE_BREAK()
        ;
        if ((*ProCategories[yx].Output)(yx, Accumulator & BITS26))
          EmulatorTimeout = 1;
        if (ClearAcc)
          Accumulator = (Accumulator & ~BITS26);
        break;
      case PRO_INPUT:
        ACC_WRITE_BREAK()
        ;
        PRO_ANY_BREAK ()
        ;
        if (ClearAcc)
          Accumulator = (Accumulator & ~BITS26);
        if ((*ProCategories[yx].Input)(yx, &InputValue))
          EmulatorTimeout = 1;
        Accumulator |= InputValue;
        break;
      case PRO_TRS_PULSES:
        EmulatorUnimplementedYX = 1;
        break;
      case PRO_REENT_ATM:
        EmulatorUnimplementedYX = 1;
        break;
        }
    }
    break;
  case 03: // RSU
    {
      int Minuend, Subtrahend, Difference;
      DATA_ANY_BREAK();
      ACC_WRITE_BREAK();
      Minuend = Data;
      Subtrahend = Accumulator & BITS26;
      if (Subtrahend & BIT26)
        Subtrahend |= ~BITS26; // Sign extend.
      Difference = Minuend - Subtrahend;
      Accumulator = (Accumulator & ~BITS26) | (Difference & BITS26);
    }
    break;
  case 04: // ADD
    {
      int Addend1, Addend2, Sum;
      DATA_ANY_BREAK();
      ACC_WRITE_BREAK();
      Addend1 = Accumulator & BITS26;
      if (Addend1 & BIT26)
        Addend1 |= ~BITS26; // Sign extend.
      Addend2 = Data;
      Sum = Addend1 + Addend2;
      Accumulator = (Accumulator & ~BITS26) | (Sum & BITS26);
    }
    break;
  case 05: // SUB
    {
      int Minuend, Subtrahend, Difference;
      DATA_ANY_BREAK();
      ACC_WRITE_BREAK();
      Minuend = Accumulator & BITS26;
      if (Minuend & BIT26)
        Minuend |= ~BITS26; // Sign extend.
      Subtrahend = Data;
      Difference = Minuend - Subtrahend;
      Accumulator = (Accumulator & ~BITS26) | (Difference & BITS26);
    }
    break;
  case 06: // CLA
    DATA_ANY_BREAK()
    ;
    ACC_WRITE_BREAK()
    ;
    Accumulator = (Accumulator & ~BITS26) | (Data & BITS26);
    break;
  case 07: // AND
    {
      int Addend1, Addend2, Sum;
      DATA_ANY_BREAK();
      ACC_WRITE_BREAK();
      Addend1 = Accumulator & BITS26;
      Addend2 = Data;
      Sum = Addend1 & Addend2;
      Accumulator = (Accumulator & ~BITS26) | (Sum & BITS26);
    }
    break;
  case 010: // MPY
    {
      int Factor1, Factor2, Product;
      DATA_ANY_BREAK();
      ACC_ANY_BREAK();
      PQ_WRITE_BREAK();
      Factor1 = Accumulator & BITS26;
      Factor2 = Data;
      Product = Factor1 * Factor2;
      PqRegister = (PqRegister & ~BITS26) | (Product & BITS26);
      PqRegister |= 0x30000000;
    }
    break;
  case 011: // TRA
    Jump: ;
    memcpy(&JumpHOP, &HopRegister, sizeof(Address_t));
    if (Value & 0400)
      JumpHOP.Page = RESIDUAL_SECTOR;
    JumpHOP.Word = Value & 0377;
    WasJump = 1;
    break;
  case 012: // SHF
    ACC_WRITE_BREAK()
    ;
    ValueToStore = Accumulator & BITS26;
    // Sign-extend bit 26 to all 32 bits of the variable.
    if (ValueToStore & BIT26)
      ValueToStore |= ~BITS26;
    switch (Value)
      {
    case 021:
      ValueToStore = ValueToStore >> 1;
      break;
    case 020:
      ValueToStore = ValueToStore >> 2;
      break;
    case 030:
    case 031:
    case 032:
    case 033:
    case 034:
    case 035:
    case 036:
    case 037:
      ValueToStore = ValueToStore << 1;
      break;
    case 040:
    case 041:
    case 042:
    case 043:
    case 044:
    case 045:
    case 046:
    case 047:
      ValueToStore = ValueToStore << 2;
      break;
    default:
      // This behavior is actually specified: Illegal YX
      // clears the accumulator.
      ValueToStore = 0;
      break;
      }
    Accumulator = (Accumulator & ~BITS26) | (ValueToStore & BITS26);
    break;
  case 013: // TMI
    ACC_ANY_BREAK()
    ;
    if (Accumulator & BIT26)
      goto Jump;
    break;
  case 014: // STO
    ACC_ANY_BREAK()
    ;
    ValueToStore = Accumulator;
    //printf("D STO %011o\n", ValueToStore);
    Store: ;
    if (!hwm)
      {
        uint16_t Val0, Val1;
        if (*dMem0 == ILLEGAL_VALUE)
          *dMem0 = 0;
        if (*dMem1 == ILLEGAL_VALUE)
          *dMem1 = 0;
        //printf("D *dMem0 %05o\n", *dMem0);
        //printf("D *dMem1 %05o\n", *dMem1);
        Val0 = (*dMem0 & ~BITS13) | (ValueToStore & BITS13);
        Val1 = (*dMem1 & ~BITS13) | ((ValueToStore >> 13) & BITS13);
        //printf("D Val0 %05o\n", Val0);
        //printf("D Val1 %05o\n", Val1);
        if (!IgnoreBreakpoint)
          if ((*dMem0 != ILLEGAL_VALUE && (*dMem0 & BREAK13) != 0) || (*dMem1
              != ILLEGAL_VALUE && (*dMem1 & BREAK13) != 0))
            {
              if (WatchmodeType != WT_CHANGE || *dMem0 != Val0 || *dMem1
                  != Val1)
                {
                  EmulatorWatchpoint = 1;
                  return;
                }
            }
        *dMem0 = Val0;
        *dMem1 = Val1;
      }
    break;
  case 015: // SPQ
    PQ_ANY_BREAK()
    ;
    ValueToStore = PqRegister;
    //printf("D SPQ %011o\n", ValueToStore);
    goto Store;
  case 016: // CLD
    {
      int yx;
      int32_t InputValue;
      yx = Value & 077;
      ACC_WRITE_BREAK();
      CLD_ANY_BREAK();
      if ((*CldCategories[yx].Input)(yx, &InputValue))
        EmulatorTimeout = 1;
      Accumulator = (Accumulator & ~BITS26) | (InputValue ? BITS26 : 0);
    }
    break;
  case 017: // TNZ
    ACC_ANY_BREAK()
    ;
    if (Accumulator & BITS26)
      goto Jump;
    break;
    }

  // Cleanup and prepare for next instruction.
  IgnoreBreakpoint = 0;
  CurrentCycles++;
  TotalCycles++;
  if (Run == 0)
    {
      char Input[41] = "R 0.0";
      HalSockBroadcastString(Input, strlen(Input));
      sprintf(Input, "S %lu", TotalCycles);
      HalSockBroadcastString(Input, strlen(Input));
    }
  if (WasJump)
    memcpy(&HopRegister, &JumpHOP, sizeof(Address_t));
  else
    {
      if ((HopRegister.Word & 0377) < 0377)
        HopRegister.Word++;
      else
        EmulatorEndOfSector = 1;
    }
  if (0 != (PqRegister & 0x70000000))
    PqRegister -= 0x10000000;
}

//==========================================================================

// This is where the debugger UI is completely implemented.  It communicates
// with the main program running the emulator via global variables.  There's
// a mutex associated with it, and the mutex is unlocked *only* while waiting
// for keyboard input.
void *
DebuggerThreadFunction(void *Data)
{
#define MAX_FIELDS 64
  int LastWasNext = 0;

  // Loop forever.
  pthread_mutex_lock(&DebuggerMutex);
  while (1)
    {
      char *s, *ss, c, *Fields[MAX_FIELDS];
      uint32_t *Mem32;
      uint16_t *Mem16M, *Mem16L;
      Address_t *MemAddress;
      int NumFields = 0;

      //DisplayDebuggerPrompt();
      pthread_mutex_unlock(&DebuggerMutex);
      s = fgets(DebuggerUserInput, sizeof(DebuggerUserInput), stdin);
      pthread_mutex_lock(&DebuggerMutex);
      if (NULL == s)
        continue;
      if (Run)
        {
          DebuggerPause = 1;
          pthread_mutex_unlock(&DebuggerMutex);
          while (DebuggerPause)
            SleepMilliseconds(10);
          pthread_mutex_lock(&DebuggerMutex);
        }

      // Normalize the input string somewhat by trimming off leading spaces
      // and trailing CR or LF.
      for (s = DebuggerUserInput; *s; s++)
        {
          if (*s == '\n' || *s == '\r')
            {
              *s = 0;
              break;
            }
        }
      for (s = DebuggerUserInput; isspace (*s); s++)
        ;

      // Parse the input line into NUL-terminated fields.  Start the
      // loop with s pointing to the first field.
      for (NumFields = 0; *s && *s != '#' && NumFields < MAX_FIELDS; NumFields++)
        {
          for (ss = s; *ss && !isspace (*ss); ss++)
            ; // Find end of the field.
          c = *ss;
          *ss = 0;
          Fields[NumFields] = s;
          if (c == 0)
            s = ss;
          else
            for (s = ss + 1; isspace (*s); s++)
              ;
        }

      // Convert the first field, which contains the command, to upper case.
      if (NumFields >= 1)
        for (s = Fields[0]; *s; s++)
          *s = toupper(*s);

      // Interpret the command entered by the user.
      if (NumFields < 1)
        {
          if (LastWasNext)
            goto Step;
        }
      else if (LastWasNext = 0, (!strcmp(Fields[0], "EXIT") || !strcmp(
          Fields[0], "QUIT")))
        {
          WriteBinaryFile("yaOBC.bin");
          WriteIoFile("yaOBC.io");
          DebuggerQuit = 1;
          break;
        }
      else if (!strcmp(Fields[0], "RUN") || !strcmp(Fields[0], "CONT")
          || !strcmp(Fields[0], "R"))
        {
          char Input[41];
          sprintf(Input, "R %lf", Speedup);
          HalSockBroadcastString(Input, strlen(Input));
          DebuggerRun = 1;
          IgnoreBreakpoint = 1;
          LastDebuggedHopRegister.Word ^= 0377;
          printf("Emulation running ...\n");
        }
      else if (!strcmp(Fields[0], "STEP") || !strcmp(Fields[0], "NEXT")
          || !strcmp(Fields[0], "S") || !strcmp(Fields[0], "N"))
        {
          Step: ;
          LastWasNext = 1;
          if (NumFields < 2)
            StepN = 1;
          else
            StepN = atoi(Fields[1]);
          IgnoreBreakpoint = 1;
          LastDebuggedHopRegister.Word ^= 0377;
        }
      else if (!strcmp(Fields[0], "BREAK"))
        {
          int Set = 0;
          if (ParseLocation(Fields[1], &Mem32, &Mem16M, &Mem16L, &MemAddress))
            printf("Can't parse Location field (%s).\n", Fields[1]);
          else
            {
              if (Mem32 != NULL)
                {
                  *Mem32 |= BREAK26;
                  Set = 1;
                }
              if (Mem16M != NULL)
                {
                  *Mem16M |= BREAK13;
                  Set = 1;
                }
              if (Mem16L != NULL)
                {
                  *Mem16L |= BREAK13;
                  Set = 1;
                }
              if (MemAddress != NULL)
                {
                  if (MemAddress == &HopRegister)
                    printf("Location %s not eligible for a breakpoint.\n",
                        Fields[1]);
                }
              if (Set)
                printf("Breakpoint set at location %s.\n", Fields[1]);
            }
        }
      else if (!strcmp(Fields[0], "WATCHMODE"))
        {
          if (!strcasecmp(Fields[1], "ANY"))
            WatchmodeType = WT_ANY;
          else if (!strcasecmp(Fields[1], "WRITE"))
            WatchmodeType = WT_WRITE;
          else if (!strcasecmp(Fields[1], "CHANGE"))
            WatchmodeType = WT_CHANGE;
          else
            printf("%s is not a valid WATCHMODE mode.\n", Fields[1]);
        }
      else if (!strcmp(Fields[0], "BREAKPOINTS"))
        {
          int yx, m, p, s, w;
          for (m = 0; m < MAX_MODULES; m++)
            for (p = 0; p < MAX_SECTORS; p++)
              for (s = 0; s < MAX_SYLLABLES; s++)
                for (w = 0; w < MAX_WORDS; w++)
                  if (Binary[m][p][s][w] != ILLEGAL_VALUE
                      && (Binary[m][p][s][w] & BREAK13) != 0)
                    printf("Breakpoint at %o-%02o-%o-%03o.\n", m, p, s, w);
          for (yx = 0; yx < MAX_YX; yx++)
            {
              if (0 != (ProCategories[yx].Value & BREAK26))
                printf("Breakpoint at PRO%02o.\n", yx);
            }
          for (yx = 0; yx < MAX_YX; yx++)
            {
              if (0 != (CldCategories[yx].Value & BREAK26))
                printf("Breakpoint at CLD%02o.\n", yx);
            }
          if ((Accumulator & BREAK26) != 0)
            printf("Breakpoint at ACC.\n");
          if ((PqRegister & BREAK26) != 0)
            printf("Breakpoint at PQ.\n");
        }
      else if (!strcmp(Fields[0], "DELETE"))
        {
          if (NumFields == 1)
            {
              uint16_t *Mem;
              int i;
              Mem = &Binary[0][0][0][0];
              for (i = 0; i < 8 * 16 * 3 * 256; i++, Mem++)
                if (*Mem != ILLEGAL_VALUE)
                  *Mem &= ~BREAK13;
              for (i = 0; i < MAX_YX; i++)
                {
                  ProCategories[i].Value &= ~BREAK26;
                  CldCategories[i].Value &= ~BREAK26;
                }
              Accumulator &= ~BREAK26;
              PqRegister &= ~BREAK26;
            }
          else
            {
              if (ParseLocation(Fields[1], &Mem32, &Mem16M, &Mem16L,
                  &MemAddress))
                printf("Can't parse Location field (%s).\n", Fields[1]);
              else
                {
                  if (Mem32 != NULL)
                    *Mem32 &= ~BREAK26;
                  else if (Mem16M != NULL)
                    *Mem16M &= ~BREAK13;
                  else if (Mem16L != NULL)
                    *Mem16L &= ~BREAK13;
                }
            }
        }
      else if (!strcmp(Fields[0], "PRINT"))
        {
          uint32_t Value;
          if (ParseLocation(Fields[1], &Mem32, &Mem16M, &Mem16L, &MemAddress))
            printf("Can't parse Location field %s.\n", Fields[1]);
          else
            {
              if (Mem32 != NULL)
                printf("%s: %09o\n", Fields[1], (*Mem32 & BITS26));
              else if (Mem16M != NULL)
                {
                  Value = ((*Mem16M & BITS13) << 13) | (*Mem16L & BITS13);
                  printf("%s: %09o\n", Fields[1], Value);
                }
              else if (Mem16L != NULL)
                printf("%s: %05o\n", Fields[1], (*Mem16L & BITS13));
              else if (MemAddress == &HopRegister)
                {
                  BuildObcHopRegisterFromAddress(MemAddress, &Value);
                  printf("%s: %09o\n", Fields[1], Value);
                }
            }
        }
      else if (!strcmp(Fields[0], "EDIT"))
        {
          if (ParseLocation(Fields[1], &Mem32, &Mem16M, &Mem16L, &MemAddress))
            printf("Can't parse Location field %s.\n", Fields[1]);
          else
            {
              int Value;
              if (ParseValue(Fields[2], &Value))
                printf("Can't parse Value field %s.\n", Fields[2]);
              else
                {
                  if (Mem32 != NULL)
                    *Mem32 = (*Mem32 & ~BITS26) | (Value & BITS26);
                  else if (Mem16M != NULL)
                    {
                      *Mem16M = (*Mem16M & ~BITS13) | ((Value >> 13) & BITS13);
                      *Mem16L = (*Mem16L & ~BITS13) | (Value & BITS13);
                    }
                  else if (Mem16L != NULL)
                    *Mem16L = (*Mem16L & ~BITS13) | (Value & BITS13);
                  else if (MemAddress == &HopRegister)
                    BuildAddressFromObcHopRegister(Value, MemAddress);
                }
            }
        }
      else if (!strcmp(Fields[0], "COREDUMP"))
        {
          if (!WriteBinaryFile(Fields[1]))
            printf("Snapshot file %s created.\n", Fields[1]);
          if (NumFields > 1 && !WriteIoFile(Fields[2]))
            printf("I/O file %s created.\n", Fields[2]);
        }
      else if (!strcmp(Fields[0], "ATM"))
        {
          int Module, i;
          uint16_t *Source, *Dest;
          Module = atoi(Fields[1]);
          if (Module < 1 || Module > 7)
            printf("The module to load must be in the range 1-7.\n");
          else
            {
              Source = &Binary[Module][0][0][0];
              Dest = &Binary[0][0][0][0];
              for (i = 0; i < 16 * 3 * 256; i++, Source++, Dest++)
                if (*Source != ILLEGAL_VALUE)
                  *Dest = *Source;
            }
        }
      else if (!strcmp(Fields[0], "SPEED"))
        {
          Speedup = atof(Fields[1]);
        }
      else if (!strcmp(Fields[0], "HELP") || !strcmp(Fields[0], "MENU")
          || !strcmp(Fields[0], "?"))
        {
          printf("Debugger commands:\n"
            "\tHELP or MENU or ? -- Display available commands.\n"
            "\tQUIT or EXIT -- Exit the yaOBC program, saving the current state.\n"
            "\tRUN or CONT or R -- Stop pausing and resume running the emulation.\n"
            "\tSTEP [N] or NEXT [N] or S [N] or N [N] -- Emulate next N instructions.\n"
            "\tBREAK Location -- Set breakpoint.\n"
            "\tWATCHMODE Mode -- Data-access types (ANY/WRITE/CHANGE) triggering breaks.\n"
            "\tBREAKPOINTS -- Display all breakpoints.\n"
            "\tDELETE [Location] -- Delete breakpoint at Location, or all.\n"
            "\tPRINT Location -- Display contents of Location.\n"
            "\tEDIT Location Value -- Modify contents of Location.\n"
            "\tCOREDUMP File [IoFile] -- Save system snapshot files.\n"
            "\tATM Module -- Load program Module from ATM into main memory.\n"
            "\tSPEED X -- Emulate at X times real time. (1.0 is normal.)\n"
            "Location formats:\n"
            "\tLeft-hand symbol, constant name, variable name\n"
            "\t[D-]Module-Sector-Syllable-Word\n"
            "\tHOP, ACC, PQ\n"
            "\tPROYX, CLDYX\n"
            "Value for EDIT:\n"
            "\tOctal constant with leading 0\n"
            "\tDecimal constant without decimal point -> unscaled\n"
            "\tDecimal constant with decimal point -> scaled\n"
            "\tLeft-hand symbol -> HOP constant\n"
            "\tVariable/constant name -> contents of the variable/constant\n"
            "\t[H-]Module-Sector-Syllable-Word -> HOP constant\n");
        }
      else
        printf("Unknown debugger command: %s\n", Fields[0]);

      if (!StepN)
        DisplayDebuggerPrompt();
    }

  pthread_mutex_unlock(&DebuggerMutex);
  return (NULL);
}

//==========================================================================

// Parse a Location string as described at
// http://www.ibiblio.org/apollo/Gemini.html#Location-Field_Parsing_by_the_Debugger.
// Return 0 on success, or non-zero on error.  If not error, set the MemXXX
// pointers as follows:
//      Mem32: 26-bit value in a 32-bit word.
//      Mem16L: 13-bit value in a 16-bit word.
//      Mem16M and Mem16L: 26-bit value in two 16-bit words with 13 bits each.
//      MemAddress: Address_t structure not part of a Symbol_t. (HopRegister)
// and set the other pointers to NULL.
int
ParseLocation(char *Location, uint32_t **Mem32, uint16_t **Mem16M,
    uint16_t **Mem16L, Address_t **MemAddress)
{
  int m, p, s, w, yx;
  Symbol_t Key, *SearchResult;

  *Mem32 = NULL;
  *Mem16M = NULL;
  *Mem16L = NULL;
  *MemAddress = NULL;

  if (!strcasecmp(Location, "HOP"))
    {
      *MemAddress = &HopRegister;
      return (0);
    }
  if (!strcasecmp(Location, "ACC"))
    {
      *Mem32 = &Accumulator;
      return (0);
    }
  if (!strcasecmp(Location, "PQ"))
    {
      *Mem32 = &PqRegister;
      return (0);
    }
  if (4 == sscanf(Location, "%o-%o-%o-%o", &m, &p, &s, &w) && m >= 0 && m
      < MAX_MODULES && p >= 0 && p < MAX_SECTORS && s >= 0 && s < MAX_SYLLABLES
      && w >= 0 && w < MAX_WORDS)
    {
      *Mem16L = &Binary[m][p][s][w];
      return (0);
    }
  if (!strncasecmp(Location, "D-", 2) && 4 == sscanf(Location + 2,
      "%o-%o-%o-%o", &m, &p, &s, &w) && m >= 0 && m < MAX_MODULES && p >= 0
      && p < MAX_SECTORS && s == 0 && w >= 0 && w < MAX_WORDS)
    {
      *Mem16M = &Binary[m][p][1][w];
      *Mem16L = &Binary[m][p][0][w];
      return (0);
    }
  if (!strncasecmp(Location, "PRO", 3) && 1 == sscanf(Location + 3, "%o", &yx)
      && yx >= 0 && yx < MAX_YX)
    {
      *Mem32 = (uint32_t*) &ProCategories[yx].Value;
      return (0);
    }
  if (!strncasecmp(Location, "CLD", 3) && 1 == sscanf(Location + 3, "%o", &yx)
      && yx >= 0 && yx < MAX_YX)
    {
      *Mem32 = (uint32_t*) &CldCategories[yx].Value;
      return (0);
    }

  // Search for symbol-name.
  strcpy(Key.Name, Location);
  SearchResult = bsearch(&Key, Symbols, NumSymbols, sizeof(Symbol_t),
      CompareSymbols);
  if (SearchResult != NULL)
    {
      if (SearchResult->Type == ST_CODE)
        {
          Mem13: ;
          *Mem16L
              = &Binary[SearchResult->Address.Module][SearchResult->Address.Page][SearchResult->Address.Syllable][SearchResult->Address.Word];
          if (**Mem16L == ILLEGAL_VALUE)
            {
              printf("FYI: Location %s (LSW) was previously uninitialized.\n",
                  Location);
              **Mem16L = 0;
            }
        }
      else if (SearchResult->Type == ST_CONSTANT || SearchResult->Type
          == ST_VARIABLE)
        {
          if (SearchResult->Address.Syllable == 2)
            goto Mem13;
          *Mem16M
              = &Binary[SearchResult->Address.Module][SearchResult->Address.Page][1][SearchResult->Address.Word];
          if (**Mem16M == ILLEGAL_VALUE)
            {
              printf("FYI: Location %s (MSW) was previously uninitialized.\n",
                  Location);
              **Mem16M = 0;
            }
          goto Mem13;
        }

      return (0);
    }

  printf("Note that symbols are case-sensitive to the debugger.\n");
  return (1); // No matches, error.
}

//==========================================================================

// Parse a Value string for the debugger's EDIT command as described at
// http://www.ibiblio.org/apollo/Gemini.html#Commands_Recognised_by_the_yaOBC.
// Return 0 on success, or non-zero on error.  If not error, set Value.
int
ParseValue(char *ValueString, int *Value)
{
  int m, p, s, w;
  Symbol_t Key, *Result;

  if (ValueString[0] == '0' && strspn(ValueString, "01234567") == strlen(
      ValueString))
    {
      sscanf(ValueString, "%o", Value);
      return (0);
    }
  if ((ValueString[0] == '+' || ValueString[0] == '-'
      || isdigit (ValueString[0])) && strspn(ValueString + 1, "0123456789")
      == strlen(ValueString + 1))
    {
      if (1 != sscanf(ValueString, "%d", Value))
        return (1);
      return (0);
    }
  if ((ValueString[0] == '+' || ValueString[0] == '-' || ValueString[0] == '.'
      || isdigit (ValueString[0])) && strspn(ValueString + 1, ".0123456789")
      == strlen(ValueString + 1))
    {
      double f;
      if (1 != sscanf(ValueString, "%lf", &f))
        return (1);
      if (f == 0.0)
        *Value = 0;
      else
        {
          while (fabs(f) >= 1.0)
            f /= 2.0;
          while (fabs(f) < 0.5)
            f *= 2.0;
          *Value = lround(f * (1 << 25));
        }
      return (0);
    }
  if (((!strncasecmp(ValueString, "H-", 2) && 4 == sscanf(ValueString + 2,
      "%o-%o-%o-%o", &m, &p, &s, &w)) || 4 == sscanf(ValueString,
      "%o-%o-%o-%o", &m, &p, &s, &w)) && m >= 0 && m < MAX_MODULES && p >= 0
      && p < MAX_SECTORS && s >= 0 && s < MAX_SYLLABLES && w >= 0 && w
      < MAX_WORDS)
    {
      uint32_t Value32;
      Address_t Address;
      Address.Module = m;
      Address.Page = p;
      Address.Syllable = s;
      Address.Word = w;
      Address.HalfWordMode = (0 == strncasecmp(ValueString, "H-", 2));
      BuildObcHopRegisterFromAddress(&Address, &Value32);
      *Value = Value32;
      return (0);
    }
  strcpy(Key.Name, ValueString);
  Result = bsearch(&Key, Symbols, NumSymbols, sizeof(Symbol_t), CompareSymbols);
  if (Result != NULL)
    {
      if (Result->Type == ST_CODE)
        {
          uint32_t Value32;
          BuildObcHopRegisterFromAddress(&Result->Address, &Value32);
          *Value = Value32;
          return (0);
        }
      else if (Result->Type == ST_CONSTANT || Result->Type == ST_VARIABLE)
        {
          uint16_t *Mem16M, *Mem16L, Zero = 0;
          if (Result->Address.Syllable == 2) // Half-word
            {
              Mem16M = &Zero;
              Mem16L
                  = &Binary[Result->Address.Module][Result->Address.Page][2][Result->Address.Word];
              if (*Mem16L == ILLEGAL_VALUE)
                {
                  printf(
                      "FYI: Location %s (LSW) was previously uninitialized.\n",
                      ValueString);
                  *Mem16L = 0;
                }
            }
          else // full-word
            {
              Mem16M
                  = &Binary[Result->Address.Module][Result->Address.Page][1][Result->Address.Word];
              if (*Mem16M == ILLEGAL_VALUE)
                {
                  printf(
                      "FYI: Location %s (MSW) was previously uninitialized.\n",
                      ValueString);
                  *Mem16M = 0;
                }
              Mem16L
                  = &Binary[Result->Address.Module][Result->Address.Page][0][Result->Address.Word];
              if (*Mem16L == ILLEGAL_VALUE)
                {
                  printf(
                      "FYI: Location %s (LSW) was previously uninitialized.\n",
                      ValueString);
                  *Mem16L = 0;
                }
            }
          *Value = ((*Mem16M & BITS13) << 13) | (*Mem16L & BITS13);
          return (0);
        }
    }
  return (1);
}

//==========================================================================

void
SleepMilliseconds(unsigned Milliseconds)
{
  if (Milliseconds == 0)
    return;
#ifdef WIN32
  Sleep (Milliseconds);
#else
  struct timespec Req, Rem;
  Req.tv_sec = Milliseconds / 1000;
  Req.tv_nsec = (Milliseconds % 1000) * 1000 * 1000; // 10 milliseconds.
  nanosleep(&Req, &Rem);
#endif
}

//==========================================================================

// Hardware-abstraction: method types for the --method command-line switch.
// All functions return:
//      0 on success
//      1 on errors such as bad YX
//      2 on timeout
// Note that while the CPU doesn't support anything for discrete outputs,
// we nevertheless provide a function for it, for use by the debugger.

// These particular HAL functions are for type=SOCK.

static ENetHost *host = NULL;

// The thread function that services the enet server.
void *
HalSockThreadFunction(void *Data)
{
  double f;
  int yx, b;
  unsigned long c, d, Order = 0;
  HalSockEvent_t Event;

  while (1)
    {
      ENetEvent event;
      int EventFound = 0;
      char Input[41];

      // Service the host for incoming packets, connects, disconnects.
      enet_host_service(host, &event, 1000);
      switch (event.type)
        {
      case ENET_EVENT_TYPE_CONNECT:
        if (Verbosity)
          {
            printf("A new client connected from 0x%08X:%u.\n",
                event.peer -> address.host, event.peer -> address.port);
            NeedDebuggerPrompt = 1;
          }
        sprintf(Input, "S %lu", TotalCycles);
        HalSockBroadcastString(Input, strlen(Input));
        if (Run)
          sprintf(Input, "R %lf", Speedup);
        else
          sprintf(Input, "R 0.0");
        HalSockBroadcastString(Input, strlen(Input));
        break;

      case ENET_EVENT_TYPE_RECEIVE:
        if (Verbosity > 5)
          {
            printf("%zu 0x%08X:%u \"%s\"\n", event.packet -> dataLength,
                event.peer -> address.host, event.peer -> address.port,
                event.packet -> data);
            NeedDebuggerPrompt = 1;
          }
        // Interpret the incoming packet.
        if (1 == sscanf((char *) event.packet->data, "R %lf", &f))
          SetCyclesPerTick(f);
        else if (3 == sscanf((char *) event.packet->data, "D%02o%1o %lu", &yx,
            &b, &c) && yx <= 077 && b <= 1)
          {
            EventFound = 1;
            Event.Type = 1;
            Event.Data = b;
          }
        else if (3 == sscanf((char *) event.packet->data, "P%02o %lu %lu", &yx,
            &d, &c) && yx <= 077 && d <= 0377777777)
          {
            EventFound = 1;
            Event.Type = 0;
            Event.Data = d;
          }
        if (EventFound)
          {
            Event.yx = yx;
            Event.Count = c;
            Event.Order = Order++;
            pthread_mutex_lock(&HalSockMutex);
            if (NumHalSockEvents < MAX_HALSOCK_EVENTS)
              {
                memcpy(&HalSockEvents[NumHalSockEvents++], &Event,
                    sizeof(Event));
                qsort(HalSockEvents, NumHalSockEvents, sizeof(HalSockEvent_t),
                    HalSockEventCmp);
              }
            else if (Verbosity)
              {
                printf("Event queue full, dropping \"%s\".\n",
                    event.packet->data);
                NeedDebuggerPrompt = 1;
              }
            pthread_mutex_unlock(&HalSockMutex);
          }

        /* Clean up the packet now that we're done using it. */
        enet_packet_destroy(event.packet);
        break;

      case ENET_EVENT_TYPE_DISCONNECT:
        if (Verbosity)
          {
            printf("\r0x%08X:%u disconnected.\n", event.peer -> address.host,
                event.peer -> address.port);
            NeedDebuggerPrompt = 1;
          }
        break;

      default:
        //printf ("No events.\n");
        break;
        }
    }
}

// This function initializes the enet system, sets up the
// enet server, and creates a thread to service it.
int
HalSockInitialize(void)
{
  int i, RetVal = 1;
  static int HalSockInitialized = 0;
  ENetAddress address;

  if (HalSockInitialized)
    return (0);
  pthread_mutex_lock(&HalSockMutex);

  // Initialize the socket library.
  if (enet_initialize() != 0)
    {
      printf("An error occurred while initializing ENet.\n");
      goto Error;
    }
  atexit(enet_deinitialize);

  if (Verbosity)
    {
      printf("Starting up enet server.\n");
      NeedDebuggerPrompt = 1;
    }

  /* Bind the server to the default localhost.     */
  /* A specific host address can be specified by   */
  /* enet_address_set_host (& address, "x.x.x.x"); */
  address.host = ENET_HOST_ANY;
  /* Bind the server to port. */
  address.port = Port;
  host = enet_host_create(&address, 32, 1, 0, 0);
  if (host == NULL)
    {
      printf("An error occurred while trying to create an ENet server host.\n");
      goto Error;
    }

  // Set up a thread to service the server.
  if (0 != (i = pthread_create(&HalSockThread, NULL, HalSockThreadFunction,
      NULL)))
    {
      printf("Could not create socket-driver thread (error %d).\n", i);
      goto Error;
    }

  HalSockInitialized = 1;
  RetVal = 0;
  Error: pthread_mutex_unlock(&HalSockMutex);
  return (RetVal);
}

void
HalSockBroadcastString(char *String, int Length)
{
  ENetPacket *packet;
  packet = enet_packet_create(String, Length + 1, ENET_PACKET_FLAG_RELIABLE);
  enet_host_broadcast(host, 0, packet);
  enet_host_flush(host);
}

int
ProOutputFunctionSock(int yx, int32_t Value)
{
  int i;
  char Input[41];

  if (ProOutputFunctionMem(yx, Value))
    return (1);

  i = sprintf(Input, "P%02o %09o %lu", yx, (unsigned) Value, TotalCycles);
  HalSockBroadcastString(Input, i);
  return (0);
}

int
ProInputFunctionSock(int yx, int32_t *Value)
{
  return (ProInputFunctionMem(yx, Value));
}

int
CldOutputFunctionSock(int yx, int32_t Value)
{
  int i;
  char Input[41];

  if (CldOutputFunctionMem(yx, Value))
    return (1);

  i = sprintf(Input, "D%02o%c %lu", yx, (Value ? '1' : '0'), TotalCycles);
  HalSockBroadcastString(Input, i);
  return (0);
}

int
CldInputFunctionSock(int yx, int32_t *Value)
{
  return (CldInputFunctionMem(yx, Value));
}

back to top