# You may have to edit this file to delete header lines produced by
# mailers or news systems from this file (all lines before these shell
# comments you are currently reading).

# To install this pdp8 emulator on a UNIX system:
#  1) create a directory (e.g. with the shell command mkdir stuff)
#  2) change to that directory (e.g. with the command cd stuff),
#  3) direct the remainder of this text to sh (e.g. sh < ../savedmail).
# This will make sh create files in the new directory; it will do
# nothing else (if you're paranoid, you should scan the following text
# to verify this before you follow these directions).  Then read README
# in the new directory for additional instructions.
cat > Makefile <<\xxxxxxxxxx
# make a PDP-8 emulator
#
# Instructions:
#
#   1, configure your emulator by editing this file, as directed below.
#   2, make.
#   3, make coredump if you want symbolic dumps of saved memory images.
#   4, make coremake if you want to convert RIM binaries to memory images.
#   5, make clean to remove compilation temporaries.


##########################################################################
#
# Object file name:
#
#   The absolute or relative path name of the object file.  If emulator
#   core images are marked as executable, this will be used as the name
#   of the interpreter to use when someone executes a core image.  Obvious
#   names are pdp8, pdp8e, pdp8f and pdp8m.

INTERPRETER = pdp8e


##########################################################################
#
# Emulation fudge factor:
#
#   IOFUDGE = 1 makes the emulator run with I/O timings that are generally
#   indistinguishable from real hardware, as measured by such software
#   tricks as counting iterations of a polling loop between I/O initiation
#   and completion.  IOFUDGE > 1 makes I/O run faster, desirable if your
#   emulator is slower than the original hardware and you want realistic
#   I/O behavior.
#
#   Recommended values:  For IBM RT, 20; 25Mhz SPARC, 5

# FUDGE = -DIOFUDGE=1
FUDGE = -DIOFUDGE=20


##########################################################################
#
# Machine Configuration Section:
#
#   In effect, the skeleton of the emulator is an omnibus, into which you
#   plug a number of option boards for I/O devices and other machine
#   components.
#
#   RULE:  To select an option, follow the directions.
#          If, on real hardware, you'd have to jumper something on the
#          circuit board, then you must edit the source file for the
#          option and select the jumperable feature there.


#---- exactly one of the following definition pairs must be uncommented

# KK8E -- the PDP-8/E CPU, M8300, M8310, M8320
cpu =   kk8e.o
CPU = -DKK8E

# KK8E with debug support -- the above hardware with an added trace package
#cpu =   kk8e.o debug.o
#CPU = -DKK8E -DDEBUG


#---- exactly one of the following definition triplets must be uncommented

# KC8M -- PDP-8/M Simple (non-visual) TTY oriented control panel
#console =   kc8m.o
#CONSOLE = -DKC8M
#conslib =

# KC8E -- PDP-8/E Programmer's Console (X-windows lights and switches)
console =   kc8e.o
CONSOLE = -DKC8E
conslib = -lXt -lX11


#---- any of the following internal options may be selected by including
#     their names in the definitions of intern and INTERN.

# KM8E -- Extended Memory and Time Share Option, M837

# DK8E -- Real-Time Clock, M882 or M8830
#           the interrupt rate is selectable by editing dk8e.c

intern =   km8e.o
INTERN = -DKM8E


#---- any of the following external options may be selected by including
#     their names in the defintions of extern and EXTERN.

# KL8E -- Asynchronous Console Interface, M8650
#           the baud rate is selectable by editing kl8e.c

# PC8E -- Paper-Tape Reader-Punch, M840

# RX8E -- RX01 diskette drive, M8357 interface

#extern =   kl8e.o pc8e.o
#EXTERN = -DKL8E -DPC8E
extern =   kl8e.o pc8e.o rx8e.o
EXTERN = -DKL8E -DPC8E -DRX8E


#---- Memory; the amount of memory can be selected as any multiple of 4096
#     up to 32768.  Memory runs contiguously from 0 to MAXMEM-1.
#     Values of MAXMEM over 4096 require use of the KM8E option.

MEMORY = -DMAXMEM=32768


##########################################################################
#
# Patch together the list of object files and the list of compiler
# options from the above

INTERP = -DPDP8NAME=\"$(INTERPRETER)\"
options = $(CPU) $(CONSOLE) $(INTERN) $(EXTERN) $(MEMORY) $(FUDGE) $(INTERP)
objects = $(cpu) $(console) $(intern) $(extern)
libraries = $(conslib)


##########################################################################
#
# Override make's complex understanding of file suffixes and make sure
# that all c compiles use the options we've set up.

.SUFFIXES: .o .c

.c.o:
	cc -c -O $(options) $*.c

##########################################################################
#
# Actual makefile dependencies for the PDP-8 emulator
#
# Note that, since this makefile contains the option settings,
# it references itself!

$(INTERPRETER): $(objects) realtime.o ttyaccess.o utility.o
	cc -o pdp8e $(objects) realtime.o ttyaccess.o utility.o $(libraries)

$(objects) utility.o: bus.h realtime.h Makefile

##########################################################################
#
# Secondary utilities

# this makes a utility to print an octal dump of an emulator core image
coredump: coredump.o utility.o
	cc -o coredump coredump.o utility.o

# this makes a utility to load from RIM paper tape image into emulated core
coremake: coremake.o utility.o
	cc -o coremake coremake.o utility.o

# make clean to delete the object files, saving disk space
clean:
	rm -f *.o
xxxxxxxxxx
cat > README <<\xxxxxxxxxx

This PDP8 Emulator was written by Douglas W. Jones at the University of
Iowa.  It is distributed as freeware, of uncertain function and uncertain
utility.  The current version is under developement, and this distribution
of the emulator supports only the console TTY, high speed paper tape
reader-punch, and RX01 dual 8" diskette drive.

This emulator supports two different console interfaces.  One uses X-windows
to draw a fair imitation of the front panel of a PDP-8/E computer; the other
uses the console terminal for all front panel functions.  Both versions use
standard UNIX low-level I/O to access the console terminal, they are likely
to be a bit difficult to port to non-UNIX systems.  They run well enough on
the UNIX boxes I've tried it on (a SPARC, an IBM RT and an IBM RS 6000).

Reports from Tom Karlsson <tomk@csd.uu.se> indicate that it compiles and
runs under Linux, except for one change in ttyaccess.c (noted in the
comments at the head of that file).

--------------------------------------------------------------------------
         A Painfully Brief User's Manual -- Dumb terminal version

To run the emulator, type pdp8e (the default name for the emulator),
followed, optionally, by the name of the core image file to be used.
Alternately, mark a core-image file as executable, and then run that
file; it should automatically pull in the emulator.

The console "front panel" interface to this emulator is teletype based,
using a small subset of ODT.  The following commands work:

  ?      -- on-line help, outputs an abbreviated version of the following:

  nnnnnG   -- goto location nnnnn and begin execution.
  C        -- continue from halt.

    When the emulator is running, it will not return to the "front panel"
    until either the emulator hits a halt instruction or a sequence of 5
    or so consecutive control C keys are typed (at any speed, but with no
    intervening characters of any other kind).  Single control C keys go
    to whatever software is running on the emulator at the time.

    When the machine halts, it will print "ppppp (aaaa"  -- the current
    program counter and accumulator contents.

  nnnnn/   -- open location nnnnn, print its current value.
  L        -- open the link bit and print its current value.
  A        -- open the accumulator and print its current value.
  S        -- open the switch register and print its current value.

  nnnn<cr> -- deposit nnnn in the current open location then close it.
  <cr>     -- close the current open location.
  
  D        -- list the files currently mounted on the devices.
  M        -- mount file on device -- it will prompt for device and file.

    To dismount a file from a device, mount something else (or nothing) on
    that device.

  Q        -- quit the emulator, saving the core image if possible.

    The core is considered to be a device, and files may be mounted on it
    the same way as on any other device.


--------------------------------------------------------------------------
         A Painfully Brief User's Manual -- X-Windows version

To run the emulator, type pdp8e (the default name for the emulator),
followed, optionally, by the name of the core image file to be used.
Alternately, mark a core-image file as executable, and then run that
file; it should automatically pull in the emulator.

A 630x300 window will appear on the screen, with artwork resembling the
front panel of a PDP-8/E.  (known bug:  If the artwork fails to appear,
iconify the window and then de-iconify it, or drag another window over it,
anything to force an expose event).

Along the bottom of the window are the switches of the PDP-8/E front panel
(click the mouse on one to toggle or press it).  In the center are the
address and data display lights, and on top is a bar that toggles between
an approximation of DEC's original artwork and a menu of emulator control
functions.  Clicking the mouse in this area allows you to see and change
what files are mounted on what emulated devices, to change the rate at
which the console light display is updated, and and to exit the emulator.

In general, the left mouse button (button 1) is used for emulator actions,
and clicking any other mouse button is interpreted as a help request.  The
help features of the front panel should be sufficient to explain how to
use the front panel controls.

As a rule, while the emulator is running, the command window from which
the pdp8e command was entered will be used to emulate the console teletype,
but you can also shift the input focus to the front panel window, and
anything you type there will also be interpreted as console teletype input.
This is useful on systems which insist on mapping CR to LF no matter what.


--------------------------------------------------------------------------
                             File Formats

Core files are in a simple variant of Charles Lasner's .IPL file format.
See the comments in the file utility.c for details.

Paper-tape files are 8 bit wide byte streams.  It is up to the software
running on the emulator to interpret them as ASCII, BIN, RIM or any other
format.

RX01 diskette files must begin with the 4 letters "rx01" and they must
contain at least 256 characters.  The next 251 bytes following the "rx01"
header are used for the bit-vector indicating which sectors are marked as
deleted (not that this is used anywhere).  The last of the 256 header
bytes is reserved for the hardware interleave factor (and it is currently
ignored).  The 128 byte sectors of the file follow this header, in
ascending order, starting with track 0, sector 1, then track 0 sector 2
and so on up to track 76 sector 26.  Format-time interleaving (which can't
be done on the RX01 drive), if it is ever supported, will not change the
layout of the sectors, only the access times.

--------------------------------------------------------------------------
                   A Painfully Brief Installation Manual

Once you have unpacked the shell archive, on a UNIX system, all you should
have to do is type make and the emulator will be compiled and linked under
the name pdp8e.

If, on a real machine, you would select an option by plugging in or
unplugging a board on the OMNIBUS backplane, you'd do it on the emulator by
editing the Makefile.  The most important option this gives you is the
option of using the KC8E X-windows front panel or the KC8M teletype based
front panel emulator.  Removing peripherals won't change the speed of the
emulator, but removing the KM8E memory management unit will make emulation
just a bit faster.

Any options you would have to change by fiddling with jumpers on a device
interface board on the real machine are selected in this emulator by editing
the source code.  Specifically, the emulated baud rate of the console
terminal can be changed this way.

This emulator is unkind to timesharing systems!  It runs as an infinite loop,
with no provision for reducing its load on the CPU while it waits for I/O
operations to finish.  Other users will slow the emulator down, and you'll
bother them by taking every bit of CPU time you can get for as long as you
run it.

You can adjust the effective speed of all IO devices by tweeking the fudge
factor in the makefile.  As distributed, the console terminal is set to
run at 110 baud, and you should tweek this fudge factor to make it run at
about 10 characters per second before you tweek any other device timings.
This will make all devices run at an apparent speed close to their real
speeds on DEC's hardware.  Having done this, feel free to up the baud rate
on the console terminal to 9600 baud.

The makefile includes a debugging option.  With this selected, a new
front panel command is exposed that will force the printing of a histogram
showing which instructions have been executed since the last such report
and which memory locations have been fetched from.
--------------------------------------------------------------------------
                                Contents

Makefile	-	Used by make, contains the emulator configuration.
README		-	This file.
bus.h		-	Contains definitions all "boards" share.
coredump.c	-	A utility - prints an octal dump of a saved core image.
coremake.c	-	A utility - loads a RIM file into a core image.
debug.c		-	A makefile-selectable debugging aid.
dk8e.c		-	Real time clock option.
kc8e.c		-	front panel (X-windows version).
kc8m.c		-	front panel (minimal function, dumb terminal).
kk8e.c		-	cpu.
kl8e.c		-	console interface.
km8e.c		-	memory manager.
pc8e.c		-	paper-tape reader/punch.
realtime.c	-	internal mechanisms to keep track of time.
realtime.h	-	interface to realtime.c
rx8e.c		-	rx01 interface
ttyaccess.c	-	internal mechanisms for terminal interface.
utility.c	-	internal mechanisms for command-line interface.
xxxxxxxxxx
cat > bus.h <<\xxxxxxxxxx
/* File: bus.h
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: Declarations of bus lines shared by PDP8/E and peripherals.
            This is not, strictly speaking, either an omnibus or a positive
            I/O bus, but rather, it is a set of declarations driven by the
	    needs of system emulation.
   Constraints: When included in the main program, MAIN must be defined.
            When included elsewhere, MAIN must not be defined.
		
   Based on the description in the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.
*/


/* The following trick puts extern on definitions if not in the main program
*/
#ifdef MAIN
#define EXTERN
#else
#define EXTERN extern
#endif


/******************************************/
/* Utility information needed by emulator */
/******************************************/

/* absolute path name of file where emulator object code is to be stored
*/
/*#define PDP8NAME "pdp8e"*/

/* maximum length of a sensible file name
*/
#define NAME_LENGTH 120



/*****************************************************/
/* Globals that really aren't really part of the bus */
/*****************************************************/

EXTERN char corename[NAME_LENGTH]; /* name of core image file, if any */
EXTERN char * progname; /* name of program itself (argv[0]) */


/**********/
/* Memory */
/**********/

/* This emulator does not allow for non-contiguous memory fields.
   Checking of memory addressing errors is only done when the field
   select registers are changed in KM8E or other I/O device IOT's.

   Note:  Bounds checking on memory addresses is only done when memory fields
   are selected.  Bounds checks are not needed with each memory reference.
*/
EXTERN int memory[MAXMEM];


/*******************************/
/* Generally visible registers */
/*******************************/

/* All of the following are visible outside the CPU in some context or
   another, either to some I/O device or to the front panel.
*/
EXTERN int ac;  /* the accumulator, 12 bits */
EXTERN int pc;  /* the program counter, 12 bits */
EXTERN int mq;  /* the multiplier quotient, 12 bits */
EXTERN int sr;  /* the switch register */
EXTERN int cpma;/* the memory address register */
EXTERN int mb;  /* the memory buffer register */

EXTERN int link;/* the link bit, 1 bit, in position 010000 of the word */
EXTERN int run; /* the run flipflop, 0 = halt, 1 = running */

EXTERN int enab;/* interrupt enable bit, 0 = disable, 1=enable */
EXTERN int enab_rtf; /* secodary enable needed for RTF deferred enable */
EXTERN int irq; /* the interrupt request line, 0 = no request, >0 = request */

EXTERN int sw;  /* the switch, 1 bit */

/* Note that any positive value of irq indicates a request!  Requests are
   posted by incrementing irq, and withdrawn by decrementing irq.
*/

#ifdef KM8E

/* 3 bit fields stored 12 places left so they can be ORed onto 12 addresses.
*/
EXTERN int ifr; /* the instruction field register */
EXTERN int dfr; /* the data field register */
EXTERN int ib; /* the instruction field buffer (copy to if on branch, jsr) */

/* 7 bits, exactly as documented in the small computer handbook
*/
EXTERN int sf; /* the save field register (save ir, if, df on interrupt) */

/* 1 bit, where ub is copied to uf on branch, jsr
*/
EXTERN int uf; /* the user mode flag */
EXTERN int ub; /* the user mode buffer */

/* 1 bit, reset on branch, jsr
*/
EXTERN km8e_uif; /* user interrupt flag (local to KM8E but used in KK8E) */

#endif
xxxxxxxxxx
cat > coredump.c <<\xxxxxxxxxx
/* File: coredump.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PDP-8/e emulator core file inspection program.
   Operation: read a core image file as specified on the command line and
	output a core image to standard output.
   Requires: utility.c -- the utility routines for the emulator.
*/

/* First, declare that this is a main program */
#define MAIN

#include <stdio.h>
#include "bus.h"


dump()
/* dump all of memory */
{
	int ma = 0;
	do {
		int i;
		printf( "%5.5o:", ma );
		for (i = 0; i < 8; i++) {
			printf( " %4.4o", memory[ma + i] );
		}
		printf( "\n" );

		/* now, try to find a memory address with a difference */
		do {
			ma = ma + 8;
			if (ma >= MAXMEM) { break; }
		} while (
			(memory[ma] == memory[ma-8]) &&
			(memory[ma+1] == memory[ma-7]) &&
			(memory[ma+2] == memory[ma-6]) &&
			(memory[ma+3] == memory[ma-5]) &&
			(memory[ma+4] == memory[ma-4]) &&
			(memory[ma+5] == memory[ma-3]) &&
			(memory[ma+6] == memory[ma-2]) &&
			(memory[ma+7] == memory[ma-1])
		);
	} while (ma < MAXMEM);
}

/* main program */
main(argc, argv)
int argc;
char *argv[];
{
	getargs(argc, argv);
        if (corename == NULL) { /* no file specified */
		fprintf( stderr, "%s: no core file specified\n", argv[0] );
		exit(-1);
	}
	readcore();
	dump();
}
xxxxxxxxxx
cat > coremake.c <<\xxxxxxxxxx
/* File: coremake.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PDP-8/e emulator core file creation program.
   Operation: read a .rim file from standard input and output a core image
	to the file named on the command line.
   Requires: utility.c -- the utility routines for the emulator.
*/

/* First, declare that this is a main program */
#define MAIN

#include <stdio.h>
#include "bus.h"


rim()
/* rim loader (logic reproduced as closely as possible from DEC documents) */
{
	int c, cc;
	int ma = 0;
	for (;;) {
		do {
			if ((c = getchar()) == EOF) {
				return;
			}
		} while (c > 0177);
		c = (c << 6);
		if ((cc = getchar()) == EOF) {
			return;
		}
		c = c | cc;
		if (c <= 07777) {
			memory[ma] = c;
		}
		ma = c & 07777;
	}
}

/* main program */
main(argc, argv)
int argc;
char *argv[];
{
	getargs(argc, argv);
        if (corename == NULL) { /* no file specified */
		fprintf( stderr, "%s: no core file specified\n", argv[0] );
		exit(-1);
	}
	rim();
	dumpcore();
}
xxxxxxxxxx
cat > debug.c <<\xxxxxxxxxx
/* File: debug.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: PDP-8 emulator debugging tool

*/

#include "bus.h"
#include <stdio.h>

/***********************************/
/* instruction frequency histogram */
/***********************************/

static int instructions[4096];
static int locations[32768];


/***********************************************/
/* Initialization used by CAF and reset switch */
/***********************************************/

reset_debug() /* power-on initialize */
{
	int i;
	for (i=0; i<4096; i++) {
		instructions[i] = 0;
	}
	for (i=0; i<32768; i++) {
		locations[i] = 0;
	}
}


/************************/
/* Accumulate histogram */
/************************/

accumulate_debug(p,m)
int p,m;
{
	instructions[m]++;
	locations[p]++;
}


/************************/
/* Interface to console */
/************************/

output_debug() /* show histogram */
{
	int i;
	char buf[80];

	sprintf( buf, "\r\nCumulative information since last report\r\n");
	ttyputs( buf );
	sprintf( buf, "\r\nInstructions that were fetched\r\n");
	ttyputs( buf );
	sprintf( buf, " Instruction: Frequency\r\n");
	ttyputs( buf );
	for (i=0; i<4096; i++) {
		if (instructions[i] > 0) {
			sprintf( buf, "	%04o: /%6d\r\n", i, instructions[i]);
			ttyputs( buf );
		}
	}

	sprintf( buf, "\r\nLocations that were fetched from\r\n");
	ttyputs( buf );
	sprintf( buf, "     Address: Frequency\r\n");
	ttyputs( buf );
	for (i=0; i<32768; i++) {
		if (locations[i] > 0) {
			sprintf( buf, "	%04o: /%6d\r\n", i, locations[i]);
			ttyputs( buf );
		}
	}
	reset_debug();
}
xxxxxxxxxx
cat > dk8e.c <<\xxxxxxxxxx
/* File: dk8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC DK8/EA or /EC real time clock emulator,

   Based on the description in the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.

*/

#include "realtime.h"
#include "bus.h"

/*****************************************************/
/* options:  The user may change the frequency of    */
/* simulated real time clock.  The speeds available  */
/* were as follows:                                  */
/*                                                   */
/*   DK8EA Line Frequency Clock                      */
/*     with 60 hz power, 120 ticks/second            */
/*     with 50 hz power, 100 ticks/second            */
/*                                                   */
/*   DK8EC Crystal Clock                             */
/*     1, 50, 500, 5000 ticks/second                 */
/*****************************************************/

#define ticks1 (1000 * millisecond)
#define ticks50 (20 * millisecond)
#define ticks100 (10 * millisecond)
#define ticks120 (8333 * microsecond)
#define ticks500 (2 * millisecond)
#define ticks5000 (200 * microsecond)

/* DK8/EA at 60hz */
#define tick_time = ( ticks120 / IOFUDGE )


/*********************************************************/
/* Interface between device implementation and "console" */
/*********************************************************/

/* timer used to gain control every tick */
static struct timer tick_delay;


/*************************************/
/* "officially visible" device state */
/*************************************/

static int tick_flag;
static int interrupt_enable;


/*************************/
/* Device implementation */
/*************************/

static void tick_event(p)
int p;
{ /* called from timer when a the real-time clock ticks */
	if (tick_flag == 0) {
		/* clock ticks are no-ops if the flag is already set */
		tick_flag = 1;
		if (interrupt_enable == 1) {
			irq = irq + 1;
		}
	}
	schedule( &tick_delay, tick_time, &tick_event, 0 );
}


/***********************************************/
/* Initialization used by CAF and reset switch */
/***********************************************/

dk8epower() /* power-on initialize */
{
	init_timer(tick_delay);
	schedule( &tick_delay, tick_time / 2, &tick_event, 0 );
}

dk8einit() /* console reset */
{
	tick_flag = 0;
	interrupt_enable = 1;
	/* assume that cpu clears irq for us */
}

/********************/
/* IOT Instructions */
/********************/

dk8edev(op)
int op;
{
	switch (op) {
	case 00: /* no operation */
		break;
	case 01: /* CLEI */
		if (interrupt_enable == 0) {
			interrupt_enable = 1;
			irq = irq + tick_flag;
		}
		break;
	case 02: /* CLDI */
		if (interrupt_enable == 1) {
			interrupt_enable = 0;
			irq = irq - tick_flag;
		}
		break;
	case 03: /* CLSK */
		if (tick_flag == 1) {
			pc = pc + 1;
			tick_flag = 0;
		}
		break;
	case 03: /* no operation! */
	case 04: /* no operation! */
	case 05: /* no operation! */
	case 06: /* no operation! */
	case 07: /* no operation! */
		break;
	}
}
xxxxxxxxxx
cat > kc8e.c <<\xxxxxxxxxx
/* File: kc8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC KC8/E control panel emulator, using X and Xt widgets.

   Based on the description in the PDP-8/E Maintenance manual,
   Digital Equipment Corporation, 1971, liberally interpreted.

*/

#include <stdio.h>
#include <strings.h>

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Core.h>
#define XK_MISCELLANY
#include <X11/keysymdef.h>

#include "realtime.h"
#include "bus.h"


/***************************/
/* User controlled options */
/***************************/

/* default frequency for console display update and X-events polling */
#define default_console_interval (10 * millisecond + 1)

/* height of console, in pixels, divisible by 30; width is 2.1 times this */
#define PDP8height 300


/**************************/
/* Window data structures */
/**************************/

static Widget	toplevel; 
static Widget	frontpanel; 


/*******************************/
/* tools for making frontpanel */
/*******************************/

static Display*	dpy;		/* its display */
static int	scr;		/* its screen */
static Colormap	cmap;		/* its colormap */

#define bright 65535		/* fully on color value */
static XColor grey;		/* color for for grey */
static XColor darkgrey;		/* color for for darker shade of grey */
static XColor goldenrod;	/* .. for goldenrod (when in doubt, grey) */
static XColor terracotta;	/* .. for terracotta (in doubt, dark grey) */

static char dark_bits[] = {	/* 16 by 16 bitmap data for dark shades */
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa,
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa,
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa};
static Pixmap dark_bitmap;	/* used to darken shades (uses darkbits) */

/* fonts */
static XFontStruct* headinfo;	/* info about headline font */
static XFontStruct* subinfo;	/* info about font for subheadline */
static XFontStruct* tinyinfo;	/* info about tiny font used to label things */
static XFontStruct* helpinfo;	/* info about font used for help messages */

/* all GC's have black backgrounds */
static GC whiteGC;		/* gc with white foreground, tinyfont */
static GC greyGC;		/* gc with grey foreground, tinyfont */
static GC darkgreyGC;		/* gc with dark grey foreground, tinyfont */
static GC headGC;		/* gc with big headline font */
static GC subGC;		/* gc with modest subheadline font */
static GC blackGC;		/* gc with black foreground */
static GC goldGC;		/* gc with goldenrod foreground */
static GC darkgoldGC;		/* gc with dark goldenrod foreground */
static GC terraGC;		/* gc with terracotta foreground */
static GC darkterraGC;		/* gc with dark terracotta foreground */
static GC helpGC;		/* gc used for help messages */


/*****************************************/
/* tools for filling a window with boxes */
/*****************************************/

struct box_rec {
	int x,y;          /* where to put box (upper left corner) */
	int width,height; /* how big is rectangle */
	GC boxGC;         /* GC to use */
	char rule;	  /* drawing rule 0 for outline, 1 for solid */
	char* label;	  /* character string to use as label */
	GC labGC;	  /* GC to use for label (if label != NULL) */
	XFontStruct* labinfo;	/* data about font (if label != NULL) */
	int lablen;	  /* label length (if label != NULL) */
	int labx,laby;	  /* coordinates of label (if label != NULL) */
	void (* press)(); /* hook to call when mouse click in box */
	                  /* pointer to box is passed as param to hook */
	char* help;	  /* help message to display for box */
	int state;		 /* state of box */
	struct box_rec* next;    /* pointer to next box in console */
	struct box_rec* sub;     /* pointer to subsidiary boxes */
	struct box_rec* replot;  /* pointer to box to replot (when needed) */
};
#define NOBOX (struct box_rec *)NULL
#define blankbox 0
#define linebox 1
#define fillbox 2
#define linefillbox 3
#define circlebox 4

static boxshow( box )
struct box_rec* box;
{
	struct box_rec* b;

	b = box->sub;

	if (box->rule == linebox) { /* outline */
		while (b != NOBOX) { /* put outline over sub boxes */
			boxshow( b );
			b = b->next;
		}
		XDrawRectangle( dpy, XtWindow(frontpanel), box->boxGC,
				box->x, box->y, box->width-1, box->height-1 );
	} else if (box->rule == fillbox) { /* solid */
		XFillRectangle( dpy, XtWindow(frontpanel), box->boxGC,
				box->x, box->y, box->width, box->height );
		while (b != NOBOX) { /* put sub boxes over filled region */
			boxshow( b );
			b = b->next;
		}
	} else if (box->rule == linefillbox) { /* solid outlined box */
		XFillRectangle( dpy, XtWindow(frontpanel), box->boxGC,
				box->x, box->y, box->width, box->height );
		while (b != NOBOX) { /* put outline over sub boxes */
			boxshow( b );
			b = b->next;
		}
		XDrawRectangle( dpy, XtWindow(frontpanel), whiteGC,
				box->x, box->y, box->width-1, box->height-1 );
	} else if (box->rule == circlebox) { /* lightbulb */
		XFillArc( dpy, XtWindow(frontpanel), box->boxGC,
				box->x, box->y, box->width, box->height,
				0,360*64 );
	} else {
		while (b != NOBOX) { /* put outline over sub boxes */
			boxshow( b );
			b = b->next;
		}
	}
	if (box->label != NULL) {
		XDrawString( dpy, XtWindow(frontpanel), box->labGC,
			     box->labx, box->laby, box->label, box->lablen);
	}
}

/* box records globally known to boxpress so it can implement help requests */
static struct box_rec* helpbox;
static struct box_rec* helpok;

static showhelpmsg( m )
char * m;
/* make a help message appear, with OK button armed to make it go away */
{
	helpbox->label = m;
	helpbox->lablen = strlen(helpbox->label);
	helpbox->rule = linefillbox;
	helpbox->sub = helpok;
	boxshow( helpbox );
}

static boxpress( x, y, button, box )
int x, y;
unsigned int button;
struct box_rec* box;
{
	struct box_rec* child;

	/* a mouse button was pressed in box */
	/* step 1:  was it in a sub-box of box? */
	child = box->sub;
	while (child != NOBOX) {
		if (  (x > child->x) && (x < (child->x + child->width) )
		   && (y > child->y) && (y < (child->y + child->height) )
		   ) {
			boxpress( x, y, button, child );
			return;
		}
		child = child->next;
	}

	/* step 2:  if not in a sub-box, */
	if (button == Button1) { /* take action */
		(* box->press)(box);
	} else if (box->help != NULL) { /* offer help */
		showhelpmsg( box->help );
	}
}

static struct box_rec* makebox( b )
struct box_rec* b;
/* copy from a prototype box to the real thing; used because many boxes
   can frequently be copied from one prototype, with only a few changed
   parameters for each
*/
{
	struct box_rec* n;
	XCharStruct overall;
	int dir, up, down;
	n = (struct box_rec*)malloc( sizeof(struct box_rec) );
	n->x = b->x;
	n->y = b->y;
	n->width = b->width;
	n->height = b->height;
	n->boxGC = b->boxGC;
	n->label = b->label;
	n->lablen = b->lablen;
	n->labx = b->labx;
	n->laby = b->laby;
	n->labGC = b->labGC;
	n->labinfo = b->labinfo;
	if (b->label != NULL) {
		if (b->lablen==0) {
			n->lablen = strlen(b->label);
		}
		if ((b->labx==0)||(b->laby==0)) {
			XTextExtents( n->labinfo, n->label, n->lablen,
				     &dir, &up, &down, &overall );
		}
		if (b->labx==0) {
			int w = overall.rbearing - overall.lbearing;
			n->labx = b->x+(b->width/2)-(w/2)-overall.lbearing;
		}
		if (b->laby==0) {
			int h = overall.ascent + overall.descent;
			n->laby = b->y+(b->height/2)+(h/2)-overall.descent;
		}
	}
	n->rule = b->rule;
	n->press = b->press;
	n->help = b->help;
	n->state = b->state;
	n->next = b->next;
	n->sub = b->sub;
	return n;
}

/***************************************************************/
/* tools for making PDP-8/E/F style toggle switches from boxes */
/***************************************************************/

static int toggleswitch(b)
struct box_rec* b;
/* change state of switch with top-level box b */
{
	struct box_rec* sup = b->replot;
	struct box_rec* sub = sup->sub;
	if (sub->height < (sup->height/2)) { /* switch is up, put down */
		sub->height = sup->height - sup->height/5;
		boxshow(sup);
		return 0;
	} else { /* switch is down, put up */
		sub->height = sup->height/5;
		boxshow(sup);
		return 1;
	}
}

struct box_rec* toggle = NOBOX;
static void bounceinternal()
/* called internally to make a momentary switch change back */
{
	(void) toggleswitch( toggle );
	toggle = NOBOX;
}

static void bounceswitch(b)
/* called to mark a button-press on a momentary switch */
struct box_rec* b;
{
	(void) toggleswitch(b);
	changeregs();
	toggle = b;
}

static struct box_rec* makeswitch( b, state )
struct box_rec* b;
int state;
/* called to make a switch from prototype b with initial state 0 or 1 */
{
	struct box_rec* n = makebox( b );
	struct box_rec* sub = makebox( b );

	/* n will be the box outlining the switch, sub will be the toggle */
	if (state == 0) { /* switch down */
		sub->height = b->height - b->height/5;
	} else { /* switch up */
		sub->height = b->height/5;
	}
	sub->boxGC = b->labGC;
	sub->next = NOBOX;
	sub->sub = NOBOX;
	sub->replot = n;
	n->sub = sub;
	n->replot = n;

	return n;
}


/*****************************************************/
/* code to update the display of PDP-8/E/F registers */
/*****************************************************/

/* where are boxes for each indicator light */
static struct box_rec* cpmaboxes[15];	/* bits of cpma */
static struct box_rec* runbox;		/* run */
static struct box_rec* databoxes[12];	/* data display */

/* state of data display */
static int datastate = 3;
#define showstate 0
#define showstatus 1
#define showac 2
#define showmd 3
#define showmq 4
#define showbus 5
#define showmax 6
static struct box_rec* showdatabox[showmax];  /* boxes in data select menu */

static int getdata()
{
	switch (datastate) {
	case showstate:
		return (1 << 11) | (run << 10) | (run << 9)
			| ((mb >> 3) & 0700)
			| (sw << 3);
	case showstatus:
		return (link >> 1) | ((irq == 0) << 9) | (enab << 7)
#ifdef KE8E
			| (gtf << 10)
#endif
#ifdef KM8E
			| (uf << 6) | (ifr >> 9) | (dfr >> 12)
#endif
		;
	case showac:
		return ac;
	case showmd:
		return mb;
	case showmq:
                return mq;
	case showbus:
                return mb^ac;  /* a kluge */
	}
}

static changeregs()
{
	int x;
	int data = getdata();
	struct box_rec* boxx;
	for (x = 0; x < 15; x++) {
		boxx = cpmaboxes[x];
		if (cpma & (040000 >> x)) { /* bit is one */
			if (boxx->boxGC != whiteGC) { /* bit changed */
				boxx->boxGC = whiteGC;
				boxshow( boxx );
			}
		} else { /* bit is zero */
			if (boxx->boxGC != blackGC) { /* bit changed */
				boxx->boxGC = blackGC;
				boxshow( boxx );
			}
		}
	}

	if (run != 0) { /* running */
		if (runbox->boxGC != whiteGC) {
			runbox->boxGC = whiteGC;
			boxshow( runbox );
		}
	} else { /* halted */
		if (runbox->boxGC != blackGC) {
			runbox->boxGC = blackGC;
			boxshow( runbox );
		}
	}

	for (x=0; x<12; x++) {
		if (data & (04000 >> x)) { /* bit is one */
			if (databoxes[x]->boxGC != whiteGC) {
				databoxes[x]->boxGC = whiteGC;
				boxshow( databoxes[x] );
			}
		} else { /* bit is zero */
			if (databoxes[x]->boxGC != blackGC) {
				databoxes[x]->boxGC = blackGC;
				boxshow( databoxes[x] );
			}
		}
	}
}


/**********************************************/
/* Implementation of device control interface */
/**********************************************/

struct device_rec {
	int (* mount)(); /* hook to mount file on device */
	void (* dismount)(); /* hook to dismount mounted file */
	int unit; /* thing to pass as param to mount and dismount */
	char * name; /* device name */
	char * longname; /* descriptive device name */
	char * file; /* file attached to device */
	struct device_rec * next; /* next list element */
};
static struct device_rec * devices = NULL;

register_device(m,d,u,n,l,f)
int (* m)(); /* hook to mount file on device */
void (* d)(); /* hook to dismount mounted file */
int u; /* device unit */
char * n; /* device name */
char * l; /* descriptive device name */
char * f; /* file attached to device */
{
	struct device_rec * temp;
	temp = (struct device_rec *)malloc( sizeof( struct device_rec ) );
	temp -> mount = m;
	temp -> dismount = d;
	temp -> unit = u;
	temp -> name = n;
	temp -> longname = l;
	temp -> file = f;
	if (devices == NULL) { /* make a singleton list */
		temp->next = temp;
		devices = temp;
	} else { /* add to linked list */
		temp->next = devices->next;
		devices->next = temp;
	}
}

close_devices()
{
        struct device_rec * temp = devices;
	do {
                (* (temp -> dismount))( temp -> unit );
                temp = temp -> next;
        } while (temp != devices);
}


/******************************************/
/* button press event handling procedures */
/******************************************/

static void (* keypress)() = NULL; /* hook to call for keypress (char param) */

static void backpress(b)
struct box_rec* b;
/* mouseclick in background, do nothing */
{
}

/* box records globally known to logopress so special panel can be toggled */
static struct box_rec* logobox; /* the box holding either panel or logo */
static struct box_rec* logoswap; /* alternate sublist */

static void helppress(b)
struct box_rec* b;
{
	showhelpmsg( b->help );
}

static void powerpress(b)
struct box_rec* b;
/* mouseclick in exit button or power control area, shutdown */
{
	/* ... first clean up X ... */
	powerdown();
}

#ifdef DEBUG
static void tracepress(b)
struct box_rec* b;
/* mouse click in button requesting diagnostic trace information */
{
	output_debug();
}
#endif

/* control of console refresh rate and button clicks to change it */
long int console_interval = default_console_interval;
struct box_rec* console_interval_box; /* display box for interval */

static void minuspress(b)
struct box_rec* b;
/* mouse click on minus box to decrease console interval */
{
	if (console_interval > microsecond) {
		console_interval = (console_interval * 2) / 3;
	}
	(void) sprintf( console_interval_box->label,
			"%ld", console_interval/microsecond );
	console_interval_box->lablen = strlen(console_interval_box->label);
	boxshow( console_interval_box );
}

static void pluspress(b)
struct box_rec* b;
/* mouse click on minus box to decrease console interval */
{
	if (console_interval < second) {
		console_interval = (console_interval * 3) / 2;
	}
	(void) sprintf( console_interval_box->label,
			"%ld", console_interval/microsecond );
	console_interval_box->lablen = strlen(console_interval_box->label);
	boxshow( console_interval_box );
}

/* device display control state */
struct box_rec* device_name_box; /* display box for device name */
struct box_rec* file_name_box; /* display box for file name */
static int filenameactive = 0; /* state of file name acquisition routines */

static void dnpress(b)
struct box_rec* b;
/* mouse click down box to scroll through list of devices */
{
	if (filenameactive == 0) { /* only allowed if not in mid name change */
		devices = devices->next;
		device_name_box->label = devices->name;
		device_name_box->lablen = strlen( devices->name );
		device_name_box->help = devices->longname;
		file_name_box->label = devices->file;
		file_name_box->lablen = strlen( devices->file );
		boxshow( device_name_box );
		boxshow( file_name_box );
	} else {
		showhelpmsg( "please finish typing a new file name" );
	}
}

static void filekeypress(c)
char c;
/* keypress in file name box */
{
	int pos = file_name_box->lablen;
	if (c == '\r') {
		/* done gathering file name, reset same as mouse click */
		filenameactive = 0;
		keypress = NULL;

		/* dehighlight typing box */
		file_name_box->boxGC = darkgreyGC;

		if (pos > 0) {
			file_name_box->label[ pos ] = 0;
			if(!(*(devices->mount))(devices->unit,devices->file)){
				showhelpmsg(
				 "this file cannot be mounted on that device");
			}
			file_name_box->lablen = strlen( devices->file );
		}
		boxshow( file_name_box );
	} else if ((c >= ' ') & (c <= '~')) {
		if (pos < NAME_LENGTH) {
			file_name_box->label[ pos ] = c;
			file_name_box->lablen = pos + 1;
			boxshow( file_name_box );
		}
	} else if (c == '\b') {
		if (pos > 0) {
			file_name_box->lablen = pos - 1;
			boxshow( file_name_box );
		}
	}
}

static void filepress(b)
struct box_rec* b;
/* mouseclick in the box to mount a file on a device */
{
	if (filenameactive == 0) {
		/* if not gathering file name, set state to gathering name */
		filenameactive = 1;

		/* first dismount previous file */
		(* (devices->dismount))( devices->unit );

		/* make sure result of dismount is displayed */
		file_name_box->label = devices->file;
		file_name_box->lablen = strlen( devices->file );

		/* highlight typing box */
		file_name_box->boxGC = greyGC;

		boxshow( file_name_box );
		keypress = filekeypress;
	} else {
		/* if gathering file name, finish up as if return pressed */
		int pos = file_name_box->lablen;
                filenameactive = 0;
                keypress = NULL;

                /* dehighlight typing box */
                file_name_box->boxGC = darkgreyGC;

                if (pos > 0) {
                        file_name_box->label[ pos ] = 0;
                        if(!(*(devices->mount))(devices->unit,devices->file)){
                                showhelpmsg(
                                 "this file cannot be mounted on that device");
                        }
                        file_name_box->lablen = strlen( devices->file );
                }
		boxshow( file_name_box );
	}
}

static void logopress(b)
struct box_rec* b;
/* toggle between DEC logo and special control panel */
{
	if (filenameactive == 0) { /* do it only if file name is entered */
		struct box_rec* t;
		t = logobox->sub;
		logobox->sub = logoswap;
		logoswap = t;
		boxshow( logobox );
	} else {
		showhelpmsg( "please finish typing a new file name");
	}
}

static void datapress(b)
struct box_rec* b;
/* mouseclick in data display selection area */
{
	/* unhighlight previously selected option */
	showdatabox[datastate]->rule = blankbox;

	if (b->state < 0) { /* roll through data selection options */
		datastate = (datastate + 1) % showmax;
	} else { /* select a specific data selection option */
		datastate = b->state;
	}

	/* highlight newly selected option */
	showdatabox[datastate]->rule = linebox;
	boxshow( b->replot );
	changeregs();
}

static void swpress(b)
/* mouseclick on sw switch */
struct box_rec* b;
{
	sw = toggleswitch(b);
	changeregs();
}

static void srpress(b)
/* mouseclick on some switch in switch register */
struct box_rec* b;
{
	int bit = toggleswitch(b);
	int bitnum = b->state;
	sr = (sr & ~(04000 >> bitnum)) | (bit << (11-bitnum));
}

static void addrloadpress(b)
/* mouseclick on load address switch */
struct box_rec* b;
{
	if (run == 0) {
		pc = sr;
#ifdef KM8E
		cpma = pc | ifr;
#else
		cpma = pc;
#endif
		if (cpma < MAXMEM) {
			mb = memory[cpma];
		} else {
			mb = 0;
		}
	}
	bounceswitch(b);
}

static void extdaddrloadpress(b)
/* mouseclick on load extended address switch */
struct box_rec* b;
{
#ifdef KM8E
	if (run == 0) {
		ib = (sr << 9) & 070000;
		ifr = ib;
		dfr = (sr << 12) & 070000;
		cpma = pc | ifr;
	}
#endif
	bounceswitch( b );
}

static void clearpress(b)
/* mouseclick on clear switch */
struct box_rec* b;
{
	if (run == 0) {
		clearflags();
	}
	bounceswitch( b );
}

static void contpress(b)
/* mouseclick on cont switch */
struct box_rec* b;
{
	if (run == 0) {
		run = 1;
	}
	bounceswitch( b );
}

static void exampress(b)
/* mouseclick on exam switch */
struct box_rec* b;
{
	if (run == 0) {
#ifdef KM8E
		cpma = (pc | ifr);
#else
		cpma = (pc);
#endif
		if (cpma < MAXMEM) {
			mb = memory[cpma];
		}
		pc = (pc + 1) & 07777;
#ifdef KM8E
		cpma = (pc | ifr);
#else
		cpma = (pc);
#endif
	}
	bounceswitch( b );
}

static void haltpress(b)
/* mouseclick on halt switch */
struct box_rec* b;
{
	run = 0;
	bounceswitch( b );
}

static void steppress(b)
/* mouseclick on single step switch */
struct box_rec* b;
{
	run = 2;
	bounceswitch( b );
}

static void deppress(b)
/* mouseclick on deposit switch */
struct box_rec* b;
{
	if (run == 0) {
#ifdef KM8E
		cpma = (pc | ifr);
#else
		cpma = (pc);
#endif
		mb = sr;
		if (cpma < MAXMEM) {
			memory[cpma] = mb;
		}
		pc = (pc + 1) & 07777;
#ifdef KM8E
		cpma = (pc | ifr);
#else
		cpma = (pc);
#endif
	}
	bounceswitch( b );
}

static void helpokpress(b)
/* mouseclick on ok button in help message */
struct box_rec* b;
{
	/* the following makes the help message box go away */
	/* this undoes the work done by showhelp */
	helpbox->rule = fillbox;
	helpbox->label = NULL;
	helpbox->sub = NOBOX;
	boxshow(helpbox);
	helpbox->rule = blankbox;
}



/******************************************************/
/* code to build the actual PDP-8 control panel image */
/******************************************************/

/* the top-level box on the screen */
static struct box_rec* boxlist;

static makeboxes()
#define h PDP8height /* full control panel height */
#define i (h/10)     /* top and side indent margin */
#define p (h/20)     /* partial margin indent */
#define c (h/30)     /* caption bar width */
#define t (h/80)     /* thin line width */
#define s (h/15)     /* switch width */
{
	int x;
	struct box_rec b;
	struct box_rec* sublist;

	/* box structures use indenting to show hierarchical relationship */

	b.x = i-t; b.y = i-t;
	b.width = 2*h-i+2*t; b.height = i+p;
	b.boxGC = blackGC;
	b.label = NULL;
	b.rule = linefillbox;
	b.press = backpress;
	b.help =
"This area toggles between DEC's original artwork and the emulator controls";
	b.next = NOBOX; b.sub = NOBOX;
	boxlist = makebox(&b); /* the logo bar across the top */
	{
		b.width = (4*i)/7; b.height = i-t;
		b.boxGC = whiteGC;
		b.lablen = 1;
		b.labx = 0;
		b.laby = i+s-(t/2);
		b.labGC = headGC;
		b.labinfo = headinfo;
		b.rule = linebox;
		b.press = logopress;
		b.state = 0;
		sublist = NULL; b.sub = NULL;
		for (x=0; x<7; x++) {
			b.x = 2*i+c/2+x*(4*i)/7; b.y = i;
			b.next = sublist;
			b.label = "digital"+x;
			sublist = makebox(&b); /* add logo stuff to bar */
			b.label = NULL;
		}

		b.x = i; b.y = i;
		b.width = 2*h-i; b.height = i-t;
		b.boxGC = terraGC;
		b.label = "pdp8/e";
		b.lablen = 0;
		b.labx = 6*i+s;
		b.rule = linefillbox;
		b.next = NOBOX; b.sub = sublist;
		sublist = makebox(&b); /* the logo stripe */

		b.x = i; b.y = 2*i-t;
		b.width = 2*h-i; b.height = p;
		b.boxGC = goldGC;
		b.label = "digital equipment corporation maynard massachusetts";
		b.labx = 2*i+c/2;
		b.laby = 0;
		b.labGC = subGC;
		b.labinfo = subinfo;
		b.next = sublist;
		sublist = makebox(&b); /* the company name stript */
	}
	logoswap = sublist; /* keep real logo as alternate bar here */
	{
		b.x = i; b.y = 2*i+p-s-t;
		b.width = s-t; b.height = s-t;
		b.rule = linefillbox;
		b.boxGC = darkgreyGC;
		b.label = "+";
		b.labGC = subGC;
		b.labinfo = subinfo;
		b.labx = 0;
		b.press = dnpress;
		b.help =
"click the left button here to cycle through the list of devices";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = i+s; b.y = i+c-t;
		b.width = 3*s-t; b.height = s-t;
		b.rule = blankbox;
		b.label = "DEVICE";
		b.labx = i+s+p-t;
		b.press = backpress;
		b.help =
"the device name is displayed below";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = i+4*s; b.y = i+c-t;
		b.width = 3*s-t; b.height = s-t;
		b.label = "FILE";
		b.labx = i+4*s+p;
		b.press = backpress;
		b.help =
"the file attached to the device is displayed below";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = i+s; b.y = 2*i+p-s-t;
		b.width = 3*s-t; b.height = s-t;
		b.rule = fillbox;
		b.boxGC = blackGC;
		b.label = devices->name;
		b.labx = i+s+p-t;
		b.press = backpress;
		b.help = devices->longname;
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);
		device_name_box = sublist;

		b.x = i+4*s; b.y = 2*i+p-s-t;
		b.width = 12*s-t; b.height = s-t;
		b.rule = linefillbox;
		b.boxGC = darkgreyGC;
		b.label = devices->file;
		b.labx = i+4*s+p;
		b.laby = sublist->laby; /* get good pos, label may be blank */
		b.press = filepress;
		b.help =
"click the left button here to mount a new file on the device";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);
		file_name_box = sublist;

#ifdef DEBUG
		b.x = 2*h-11*s; b.y = i;
		b.width = 2*s+s/2; b.height = s-t;
		b.rule = linefillbox;
		b.boxGC = darkgreyGC;
		b.label = "TRACE";
		b.labx = 0;
		b.laby = 0;
		b.press = tracepress;
		b.help =
"click here for a list of locations and instrucitons executed";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);
#endif

		b.x = 2*h-8*s; b.y = i;
		b.width = 2*s+s/2; b.height = s-t;
		b.rule = linefillbox;
		b.boxGC = darkgreyGC;
		b.label = "AUTHOR";
		b.labx = 0;
		b.laby = 0;
		b.press = helppress;
		b.help =
"This emulator was written by Douglas W. Jones, jones@cs.uiowa.edu";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-8*s; b.y = 2*i+p-s-t;
		b.width = 2*s+s/2; b.height = s-t;
		b.label = "CREDIT";
		b.help =
"PDP-8 and d|i|g|i|t|a|l are trademarks of Digital Equipment Corporation";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-5*s; b.y = i;
		b.width = 2*s-t; b.height = s-t;
		b.label = "EXIT";
		b.press = powerpress;
		b.help =
"click the left button here to exit the emulator (same as the power switch)";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-3*s; b.y = i;
		b.width = 2*s-t; b.height = s-t;
		b.label = "HELP";
		b.press = helppress;
		b.help =
"click the right button on any box for help message like this about that box";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-s; b.y = i;
		b.width = s; b.height = s-t;
		b.label = "OK";
		b.press = logopress;
		b.help =
"click the left button here to restore the original DEC artwork";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-5*s; b.y = 2*i+p-s-t;
		b.width = s; b.height = s-t;
		b.label = "-";
		b.press = minuspress;
		b.help =
"click the left button here to decrease the time between display updates";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);

		b.x = 2*h-4*s; b.y = 2*i+p-s-t;
		b.width = 3*s; b.height = s-t;
		b.rule = fillbox;
		b.boxGC = blackGC;
		b.label = (char *) malloc( 24 );
		(void) sprintf( b.label, "%ld", console_interval/microsecond );
		b.press = backpress;
		b.help =
"This is the time between display updates, in simulated microseconds";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);
		console_interval_box = sublist;

		b.x = 2*h-s; b.y = 2*i+p-s-t;
		b.width = s; b.height = s-t;
		b.rule = linefillbox;
		b.boxGC = darkgreyGC;
		b.label = "+";
		b.press = pluspress;
		b.help =
"click the left button here to increase the time between display updates";
		b.next = sublist; b.sub = NOBOX;
		sublist = makebox(&b);
	}
	boxlist->sub = sublist; /* patch in logo bar contents */
	logobox = boxlist;

	b.x = 5*i-t; b.y = 3*i-c;
	b.width = 8*i; b.height = c;
	b.rule = blankbox;
	b.label = "MEMORY ADDRESS";
	b.labx = 0;
	b.labGC = whiteGC;
	b.labinfo = tinyinfo;
	b.press = backpress;
	b.help = "the memory address register is displayed below here";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* title for bar above mem addr lights */


	{
		sublist = NOBOX;
		b.width = 2*i; b.height = c;
		b.rule = linefillbox;
		b.sub = NOBOX;
		b.label = "EMA";
		b.help =
"the extended memory address (IF or DF) is displayed below here";
		for (x=0; x<5; x++) {
			b.x = 3*i-t+x*2*i; b.y = 3*i;
			if (x & 1) { /* odd boxes */
				b.boxGC = terraGC;
			} else { /* even boxes */
				b.boxGC = goldGC;
			}
			b.next = sublist;
			sublist = makebox(&b); /* add color to bar */
			b.label = NULL;
			b.help =
"the memory address register is displayed below here";
		}
	}
	b.x = 3*i-t; b.y = 3*i;
	b.width = 10*i; b.height = c;
	b.rule = blankbox;
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* bar across the top of mem addr lights */

	{
		sublist = NOBOX;
		b.width = c-t; b.height = c-t;
		b.rule = circlebox;
		b.press = backpress;
		b.help = "the memory address register is displayed here";
		for (x=0; x<15; x++) {
			b.x = 3*i-t+c/2+x*s; b.y = 3*i+p;
			b.state = x;
			if (cpma & (040000 >> x)) { /* bit is one */
				b.boxGC = whiteGC;
			} else { /* bit is zero */
				b.boxGC = blackGC;
			}
			b.next = sublist;
			sublist = makebox(&b); /* one light box */
			cpmaboxes[x] = sublist;
		}
	}
	b.x = 3*i-t; b.y = 3*i+p;
	b.width = 10*i; b.height = c;
	b.rule = blankbox;
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* bar of address lights */

	b.x = 15*i+p-t-s; b.y = 3*i;
	b.width = s; b.height = c;
	b.boxGC = goldGC;
	b.label = "RUN";
	b.rule = linefillbox;
	b.help = "the machine is halted if the light below is off";
	b.next = boxlist; b.sub = NOBOX;
	boxlist = makebox(&b); /* bar across the top of run light */

	b.x = 15*i+p-t-s+c/2; b.y = 3*i+p;
	b.width = c-t; b.height = c-t;
	b.label = NULL;
	b.rule = circlebox;
	if (run == 0) {
		b.boxGC = blackGC;
	} else {
		b.boxGC = whiteGC;
	}
	b.help = "the machine is halted if this light is off";
	b.next = boxlist; b.sub = NOBOX;
	boxlist = makebox(&b); /* run light */
	runbox = boxlist;

	{
		int data = getdata();
		sublist = NOBOX;
		b.width = c-t; b.height = c-t;
		b.rule = circlebox;
		b.press = backpress;
		b.help =
"these lights display the state, status, ac, md, mq or bus";
		for (x=0; x<12; x++) {
			b.x = 5*i-t+c/2+x*s; b.y = 4*i;
			b.state = x;
			if (data & (04000 >> x)) { /* bit is one */
				b.boxGC = whiteGC;
			} else { /* bit is zero */
				b.boxGC = blackGC;
			}
			b.next = sublist;
			sublist = makebox(&b); /* one light box */
			databoxes[x] = sublist;
		}
	}
	b.x = 5*i-t; b.y = 4*i;
	b.width = 12*s; b.height = c;
	b.rule = blankbox;
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* bar of data lights */

	{
		struct box_rec* subsub;
		sublist = NOBOX;
		b.press = backpress;
		b.help =
"the above lights display the state, status, ac, md, mq or bus";
		for (x=0; x<12; x++) {
			b.x = 5*i+x*s-t; b.y = 4*i+c+t;
			b.width = s; b.height = c+c/2;
			b.boxGC = whiteGC;
			{
				static char* row[] = {
					"F","D","E",
					"IR0","IR1","IR2",
					"DIR","DATA","SW",
					"PAUSE","BRK","BRK"
				};
				b.label = row[x];
			}
			b.lablen = 0;
			b.rule = linebox;
			b.next = NOBOX; b.sub = NOBOX;
			subsub = makebox(&b); /* top caption */

			b.x = 5*i+x*s-t; b.y = 4*i+c+t+c+c/2;
			b.width = s; b.height = c;
			{
				static char* row[] = {
					"LINK","GT","INTB",
					"NOINT","ION","UM",
					"IF0","IF1","IF2",
					"DF0","DF1","DF2"
				};
				b.label = row[x];
			}
			b.rule = linebox;
			b.next = subsub; b.sub = NOBOX;
			subsub = makebox(&b); /* mid caption */

			b.x = 5*i+x*s-t ; b.y = 4*i+c+t;
			b.width = s; b.height = 2*i;
			b.label = "0 1 2 3 4 5 6 7 8 9 1011"+(2*x);
			b.lablen = 1+(x>9);
			b.rule = linefillbox;
			if (((x/3) & 1) == 0) {
				b.boxGC = terraGC;
			} else {
				b.boxGC = goldGC;
			}
			b.next = sublist; b.sub = subsub;
			sublist = makebox(&b); /* vertical bar under light */
		}

		b.x = 5*i-t+12*s; b.y = 4*i+c+t;
		b.width = 2*i+p; b.height = 2*i;
		b.boxGC = blackGC;
		b.label = NULL;
		b.rule = linefillbox;
		b.press = datapress;
		b.help =
"click the left button here to select data to display";
		b.state = -1;
		b.next = sublist; b.sub = NOBOX; /* to be filled in later! */
		sublist = makebox(&b); /* the data select switch */
		sublist->replot = sublist;

		subsub = NOBOX;
		b.width = 2*s; b.height = c;
		for (x=0; x<6; x++) {
			static char* helpmsg[] = {
"click the left button here for a display of some CPU state information",
"click the left button here for a display of the CPU status register",
"click the left button here for a display of the accumulator",
"click the left button here for a display of the memory data bus",
"click the left button here for a display of the multiplier quotient register",
"click the left button here for an approximate display of the data on the bus"
			};
			b.x = 5*i-t+12*s; b.y = 4*i+c+t+x*c;
			if (x == datastate) {
				b.rule = linebox;
			} else {
				b.rule = blankbox;
			}
			b.label = ("STATE STATUSAC    MD    MQ    BUS   ")+6*x;
			b.lablen = 6;
			b.labx = 5*i-t+12*s+c;
			b.boxGC = whiteGC;
			b.state = x;
			b.help = helpmsg[x];
			b.next = subsub; b.sub = NOBOX;
			subsub = makebox(&b); /* data select caption */
			subsub->replot = sublist;
			showdatabox[x] = subsub;
		}
		sublist->sub = subsub; /* fill in sub-boxes retrospectively */

	}
	b.x = 5*i-t; b.y = 4*i+c+t;
	b.width = 12*s+2*i+p; b.height = 2*i;
	b.label = NULL;
	b.lablen = 0;
	b.labx = 0;
	b.boxGC = whiteGC;
	b.rule = blankbox;
	b.press = backpress;
	b.help =
"the above lights display the state, status, ac, md, mq or bus";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* bar under data display lights */

	{
		b.x = i; b.y = 7*i;
		b.width = s; b.height = s+i;
		b.boxGC = terraGC;
		b.label = "OFF";
		b.laby = b.y+c+t;
		b.rule = linefillbox;
		b.press = powerpress;
		b.help = "click the left button here to exit the emulator";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* the off key position */

		b.x = i+s; b.y = 7*i;
		b.boxGC = goldGC;
		b.label = "POWER";
		b.next = sublist;
		sublist = makebox(&b); /* the power-on key position */

		b.x = i+2*s; b.y = 7*i;
		b.boxGC = terraGC;
		b.label = "LOCK";
		b.next = sublist;
		sublist = makebox(&b); /* the panel-lock key position */
	}
	b.x = i-t; b.y = 7*i;
	b.width = 3*s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.label = NULL;
	b.laby = 0;
	b.rule = linebox;
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* power control enclosing box */

	{
		b.x = 4*i-p+t; b.y = 7*i;
		b.width = s; b.height = s;
		b.boxGC = goldGC;
		b.label = "SW";
		b.rule = linefillbox;
		b.press = backpress;
		b.help =
"the SW switch, below, controls certain system options";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* the sw overbar */

		b.x = 4*i-p+2*t; b.y = 7*i+s+t;
		b.width = s-2*t; b.height = i-2*t;
		b.boxGC = darkgoldGC;
		b.label = NULL;
		b.labGC = goldGC;
		b.rule = fillbox;
		b.press = swpress;
		b.help = "click the left button here to toggle the SW switch";
		b.next = sublist; b.sub = NOBOX;
		sublist = makeswitch( &b, sw );
	}
	b.x = 4*i-p; b.y = 7*i;
	b.width = s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.labGC = whiteGC;
	b.press = backpress;
	b.help = "the SW switch controls certain system options";
	b.rule = linebox;
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* sw enclosing box */

	{
		b.x = 5*i-t; b.y = 7*i;
		b.width = 12*s; b.height = c;
		b.boxGC = terraGC;
		b.label = "SWITCH REGISTER";
		b.rule = linefillbox;
		b.press = backpress;
		b.help =
"the switch register, or SR (below) allows binary input from the front panel";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* the switch register overbar */

		b.width = s; b.height = c;
		b.press = backpress;
		b.label = NULL;
		for (x=0; x<12; x++) {
			b.x = 5*i+x*s-t ; b.y = 7*i+c;
			b.state = x;
			b.label = "0 1 2 3 4 5 6 7 8 9 1011"+(2*x);
			b.lablen = 1+(x>9);
			if (((x/3) & 1) == 0) {
				b.boxGC = terraGC;
			} else {
				b.boxGC = goldGC;
			}
			b.next = sublist;
			sublist = makebox(&b); /* switch overbar */
		}

		b.width = s-2*t; b.height = i-2*t;
		b.rule = fillbox;
		b.label = NULL;
		b.lablen = 0;
		b.press = srpress;
		b.help =
"click the left button here to toggle this bit of the switch register (SR)";
		for (x=0; x<12; x++) {
			b.x = 5*i+x*s; b.y = 7*i+s+t;
			b.state = x;
			if (((x/3) & 1) == 0) {
				b.boxGC = darkterraGC;
				b.labGC = terraGC;
			} else {
				b.boxGC = darkgoldGC;
				b.labGC = goldGC;
			}
			b.next = sublist;
			sublist = makeswitch(&b,(sr >> (11-x)) & 1);
		}
	}
	b.x = 5*i-2*t; b.y = 7*i;
	b.width = 12*s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.labGC = whiteGC;
	b.rule = linebox;
	b.press = backpress;
	b.help = "these switches are the switch register, or SR";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* switch register enclosing box */

	{
		b.x = 13*i+p; b.y = 7*i;
		b.width = s; b.height = s;
		b.boxGC = terraGC;
		b.label = "ADDR";
		b.rule = linefillbox;
		b.press = backpress;
		b.help = "the load address switch is below";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* the addr-load label */

		b.x = 13*i+p+t; b.y = 7*i+s+t;
		b.width = s-2*t; b.height = i-2*t;
		b.boxGC = darkterraGC;
		b.label = NULL;
		b.labGC = terraGC;
		b.rule = fillbox;
		b.press = addrloadpress;
		b.help =
"click the left button here to load the switch register to the program counter";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the addr-load switch */

		b.x = 13*i+p+s; b.y = 7*i;
		b.width = s; b.height = s;
		b.boxGC = goldGC;
		b.label = "EXTD";
		b.labGC = whiteGC;
		b.rule = linefillbox;
		b.press = backpress;
		b.help = "the load extended memory address switch is below";
		b.next = sublist;
		sublist = makebox(&b); /* the extd addr load label */

		b.x = 13*i+p+t+s; b.y = 7*i+s+t;
		b.width = s-2*t; b.height = i-2*t;
		b.boxGC = darkgoldGC;
		b.label = NULL;
		b.labGC = goldGC;
		b.rule = fillbox;
		b.press = extdaddrloadpress;
		b.help =
"click the left button here to load SR into the IF and DF fields of the PSW";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the extd-addr-load switch */
	}
	b.x = 13*i+p-t; b.y = 7*i;
	b.width = 2*s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.labGC = whiteGC;
	b.rule = linebox;
	b.press = backpress;
	b.help =
"these switches load address information from the switch register";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* address load enclosing box */

	{
		b.x = 15*i+p-t; b.y = 7*i;
		b.width = 2*s; b.height = c;
		b.boxGC = terraGC;
		b.label = "START";
		b.rule = linefillbox;
		b.help =
"the switches below are frequently used in starting the computer";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* caption over clear and cont */

		b.x = 15*i+p-t; b.y = 7*i+c;
		b.width = s; b.height = c;
		b.label = "CLR";
		b.help =
"the switch below clears all registers except the program counter";
		b.next = sublist;
		sublist = makebox(&b); /* caption over clear */

		b.x = 15*i+p+s-t; b.y = 7*i+c;
		b.boxGC = goldGC;
		b.label = "CONT";
		b.help =
"the switch below starts the computer running or continues from a halt";
		b.next = sublist;
		sublist = makebox(&b); /* caption over cont */

		b.x = 15*i+p+2*s-t; b.y = 7*i;
		b.width = s; b.height = s;
		b.boxGC = terraGC;
		b.label = "EXAM";
		b.help =
"the switch below reads from memory and then increments the address";
		b.next = sublist;
		sublist = makebox(&b); /* caption over exam */

		b.x = 15*i+p+3*s-t; b.y = 7*i;
		b.boxGC = goldGC;
		b.label = "HALT";
		b.help = "the switch below halts the machine";
		b.next = sublist;
		sublist = makebox(&b); /* caption over halt */

		b.x = 15*i+p+4*s-t; b.y = 7*i;
		b.boxGC = terraGC;
		b.label = "STEP";
		b.help =
"the switch below causes a single instruction (program step) to be executed";
		b.next = sublist;
		sublist = makebox(&b); /* caption over sing-step */

		b.x = 15*i+p; b.y = 7*i+s+t;
		b.width = s-2*t; b.height = i-2*t;
		b.boxGC = darkterraGC;
		b.label = NULL;
		b.labGC = terraGC;
		b.rule = fillbox;
		b.press = clearpress;
		b.help =
"click the left button here to clear all registers except the program counter";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the clear switch */

		b.x = 15*i+p+s; b.y = 7*i+s+t;
		b.boxGC = darkgoldGC;
		b.labGC = goldGC;
		b.press = contpress;
		b.help =
"click the left button here to start the computer or continue from halt";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the cont switch */

		b.x = 15*i+p+2*s; b.y = 7*i+s+t;
		b.boxGC = darkterraGC;
		b.labGC = terraGC;
		b.press = exampress;
		b.help =
"click the left button here to examine memory and then increment the address";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the exam switch */

		b.x = 15*i+p+3*s; b.y = 7*i+s+t;
		b.boxGC = darkgoldGC;
		b.labGC = goldGC;
		b.press = haltpress;
		b.help = "click the left button here to halt the machine";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the halt switch */

		b.x = 15*i+p+4*s; b.y = 7*i+s+t;
		b.boxGC = darkterraGC;
		b.labGC = terraGC;
		b.press = steppress;
		b.help =
"click the left button here to cause one instruction (program step) execute";
		b.next = sublist;
		sublist = makeswitch(&b, 1); /* the sing step switch */
	}
	b.x = 15*i+p-2*t; b.y = 7*i;
	b.width = 5*s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.labGC = whiteGC;
	b.rule = linebox;
	b.press = backpress;
	b.help = "these switches control program execution";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* start halt exam enclosing box */

	{
		b.x = 19*i+c; b.y = 7*i;
		b.width = s; b.height = s;
		b.boxGC = goldGC;
		b.label = "DEP";
		b.rule = linefillbox;
		b.press = backpress;
		b.help =
"the switch below deposits data from SR to memory then increments the address";
		b.next = NOBOX; b.sub = NOBOX;
		sublist = makebox(&b); /* label on dep box */

		b.x = 19*i+c+t; b.y = 7*i+s+t;
		b.width = s-2*t; b.height = i-2*t;
		b.boxGC = darkgoldGC;
		b.label = NULL;
		b.labGC = goldGC;
		b.rule = fillbox;
		b.press = deppress;
		b.help =
"click the left button here to load SR into memory then increment the address";
		b.next = sublist;
		sublist = makeswitch(&b, 0); /* the dep switch */
	}
	b.x = 19*i+c-t; b.y = 7*i;
	b.width = s+2*t; b.height = s+i+t;
	b.boxGC = whiteGC;
	b.labGC = whiteGC;
	b.rule = linebox;
	b.press = backpress;
	b.help =
"this switch deposits data from SR to memory and then increments the address";
	b.next = boxlist; b.sub = sublist;
	boxlist = makebox(&b); /* deposit enclosing box */

	{
		b.x = 2*h-s; b.y = h-i/2-s/2;
		b.width = s; b.height = s;
		b.boxGC = darkgreyGC;
		b.label = "OK";
		b.labGC = subGC;
		b.rule = linefillbox;
		b.labinfo = subinfo;
		b.press = helpokpress;
		b.help = "click the left button here to close the help window";
		b.next = NOBOX; b.sub = NOBOX;
		helpok = makebox(&b);
	}
	b.x = i-t; b.y = h-i+t;
	b.width = 2*h-i+2*t; b.height = i-2*t;
	b.boxGC = blackGC;
	b.rule = blankbox;
	b.label = NULL;
	b.labx = i-t+c;
	b.laby = h-i/2+t;
	b.labGC = helpGC;
	b.labinfo = helpinfo;
	b.press = backpress;
	b.help = "click the left button on the OK box to close this window";
	b.next = boxlist; b.sub = NOBOX;
	boxlist = makebox(&b); /* help message box (unused initially) */
	helpbox = boxlist;

	/* main box */

	b.x = 0; b.y = 0;
	b.width = 2*h+i; b.height = h;
	b.boxGC = whiteGC;
	b.rule = linebox;
	b.press = backpress;
	b.help = NULL;
	b.next = NOBOX; b.sub = boxlist;
	boxlist = makebox(&b); /* main box */
}
#undef h
#undef t
#undef i
#undef s


/********************************************/
/* Implementation of control panel function */
/********************************************/

static struct timer console_delay;
static struct timer halt_delay;

extern void (* ttybreak) (); /* hook to tty for keyboard overrun */

static handle_button_press(w, d, e)
Widget w;
caddr_t d;
XButtonPressedEvent *e;
{
	boxpress( e->x, e->y, e->button, boxlist );
}

static handle_button_release(w, d, e)
Widget w;
caddr_t d;
XButtonReleasedEvent *e;
{
	if (toggle != NOBOX) { /* handle undoing of button press */
		bounceinternal();
	}
}

static handle_key_press(w, d, e)
Widget w;
caddr_t d;
XKeyPressedEvent *e;
{
	int shift = (ShiftMask & e->state) != 0;
	int control = (ControlMask & e->state) != 0;
	char ch; /* the ASCII character */
	KeySym key = XKeycodeToKeysym( dpy, e->keycode, shift );
	if ((key == XK_BackSpace)||(key == XK_Left)) {
		ch = '\b';
	} else if ((key == XK_Tab)||(key == XK_Right)) {
		ch = '\t';
	} else if ((key == XK_Linefeed)||(key == XK_Down)) {
		ch = '\n';
	} else if ((key == XK_Return)||(key == XK_Next)) {
		ch = '\r';
	} else if (key == XK_Up) {
		ch = '\v';
	} else if (key == XK_Escape) {
		ch = '[' & 037;
	} else if (key == XK_Delete) {
		ch = 0177;
	} else if ((key >= ' ') && (key <= '~')) {
		if (control) {
			ch = key & 037;
		} else {
			ch = key;
		}
	} else {
		return; /* ignore all non-ascii chars */
	}

	if (keypress != NULL) {
		(* keypress)( ch );
		/* e->x and e->y are not used */
	} else {
		ttystuff( ch );
	}
}

static handle_exposure(w, d, e)
Widget w;
caddr_t d;
XExposeEvent *e;
{
	if (e->count == 0) { /* replot the whole thing */
		boxshow( boxlist );
	}
}

static void console_event()
/* all update of console display happens here! */
{
	changeregs(); /* always display any changes to registers ! */

	while (XtPending() || (run == 0)) { /* process console operations */
		XEvent event;
		XtNextEvent( &event );
		XtDispatchEvent( &event );
	}

	if (run == 2) { /* single step */
 		schedule( &console_delay, console_interval, console_event, 0 );
		countdown -= (console_interval - 1);
		run = 0;
	} else { /* normal long step */
 		schedule( &console_delay, console_interval, console_event, 0 );
	}
}

/*********************************/
/* Window manager initialization */
/*********************************/

startwindow(argc,argv)
int argc;
char **argv;
{
	Arg arg[25];
	XGCValues gcvalues;
	unsigned int n;

        /*
         *  Set up the top level and front panel widgets to house it all
         */
	toplevel = XtInitialize("PDP-8E", "PDP-8E", NULL, 0, &argc, argv);
	dpy = XtDisplay(toplevel);
	scr = DefaultScreen(dpy);
	cmap = DefaultColormap(dpy, scr);

	if ((headinfo = XLoadQueryFont( dpy,
				"-*-helvetica-bold-r-*-*-18-180-*-*-*-*-*-*" ))
	    == NULL) {
		if ((headinfo = XLoadQueryFont( dpy,
				"-*-*-*-r-*-*-16-*-*-*-*-*-*-*" ))
		    == NULL) {
			ttyrestore();exit(-1);
		}
	}

	if ((subinfo = XLoadQueryFont( dpy,
				"-*-helvetica-bold-r-*-*-10-100-*-*-*-*-*-*" ))
	    == NULL) {
		if ((subinfo = XLoadQueryFont( dpy,
				"-*-*-*-r-*-*-10-*-*-*-*-*-*-*" ))
		    == NULL) {
			ttyrestore();exit(-1);
		}
	}

        if ((tinyinfo = XLoadQueryFont( dpy,
                                "-*-clean-medium-r-*-*-6-*-*-*-*-*-*-*" ))
            == NULL) {
                if ((tinyinfo = XLoadQueryFont( dpy,
                                "-*-*-*-r-*-*-6-*-*-*-*-*-*-*" ))
                    == NULL) {
                        ttyrestore();exit(-1);
                }
        }

        if ((helpinfo = XLoadQueryFont( dpy,
                                "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-*-*" ))
            == NULL) {
                if ((helpinfo = XLoadQueryFont( dpy,
                                "-*-*-*-r-*-*-12-*-*-*-*-*-*-*" ))
                    == NULL) {
                        ttyrestore();exit(-1);
                }
        }

        n = 0;
        XtSetArg(arg[n], XtNwidth, PDP8height * 21/10);          n++;
        XtSetArg(arg[n], XtNheight, PDP8height);                 n++;
        XtSetArg(arg[n], XtNbackground, BlackPixel( dpy, scr )); n++;
        frontpanel = XtCreateWidget("panel", widgetClass, toplevel, arg, n);
	XtManageChild(frontpanel);
	XtRealizeWidget(toplevel);
	XSetWindowColormap( dpy, XtWindow(frontpanel), cmap );

	/*
	 * Create black and white Graphics Contexts
	 */

	gcvalues.foreground = BlackPixel( dpy, scr );
	gcvalues.background = BlackPixel( dpy, scr );
	gcvalues.fill_style = FillOpaqueStippled;
	blackGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground,
			&gcvalues);

	gcvalues.foreground = WhitePixel( dpy, scr );
	gcvalues.font = tinyinfo->fid;
	whiteGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFont,
			&gcvalues);

	gcvalues.font = headinfo->fid;
	headGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFont,
			&gcvalues);

	gcvalues.font = subinfo->fid;
	subGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFont,
			&gcvalues);

	gcvalues.font = helpinfo->fid;
	helpGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFont,
			&gcvalues);
	
	/*
	 * Create monochrome Graphics Contexts
	 */

	grey.red = 128*256;
	grey.green = 128*256;
	grey.blue = 128*256;
	if (  XAllocColor( dpy, cmap, &grey )
	   && (grey.pixel != WhitePixel( dpy, scr ))
	   ) { /* we got the color we want! */
		gcvalues.foreground = grey.pixel;
		greyGC = XCreateGC(dpy, XtWindow(frontpanel),
				GCForeground | GCBackground,
				&gcvalues);
	} else { /* fake it as black */
		grey.pixel = BlackPixel( dpy, scr );
		greyGC = blackGC;
	}

	darkgrey.red = 80*256;
	darkgrey.green = 80*256;
	darkgrey.blue = 80*256;
	dark_bitmap = XCreateBitmapFromData( dpy, XtWindow(frontpanel),
					     dark_bits, 16, 16);
	if (   XAllocColor( dpy, cmap, &darkgrey )
	   && (darkgrey.pixel != grey.pixel)
	   ) { /* we got the color we want */
		gcvalues.foreground = darkgrey.pixel;
		darkgreyGC = XCreateGC(dpy, XtWindow(frontpanel),
				GCForeground | GCBackground,
				&gcvalues);
	} else if (greyGC != blackGC) { /* fake it as stippled grey */
		darkgrey.pixel = grey.pixel;
		gcvalues.foreground = darkgrey.pixel;
		gcvalues.stipple = dark_bitmap;
		darkgreyGC = XCreateGC(dpy, XtWindow(frontpanel),
				GCForeground | GCBackground |
				GCFillStyle | GCStipple,
				&gcvalues);
	} else { /* give up and use black */
                darkgrey.pixel = BlackPixel( dpy, scr );
		darkgreyGC = blackGC;
	}

	/*
	 * Create color Graphics Contexts
	 */

	goldenrod.red = 224*256;
	goldenrod.green = 160*256;
	goldenrod.blue = 32*256;
	gcvalues.stipple = dark_bitmap;
	if (XAllocColor( dpy, cmap, &goldenrod )) { /* we got what we want */
		gcvalues.foreground = goldenrod.pixel;
		goldGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground,
			&gcvalues);
		darkgoldGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFillStyle | GCStipple,
			&gcvalues);
	} else if (darkgrey.pixel != grey.pixel) { /* fake it with grey */
		goldenrod.pixel = grey.pixel;
		goldGC = greyGC;
		gcvalues.foreground = grey.pixel;
		darkgoldGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFillStyle | GCStipple,
			&gcvalues);
	} else { /* fake it with grey, but give up on gold/terra difference */
		goldenrod.pixel = grey.pixel;
		goldGC = greyGC;
		darkgoldGC = darkgreyGC;
	}

	terracotta.red = 208*256;
	terracotta.green = 112*256;
	terracotta.blue = 64*256;
	gcvalues.stipple = dark_bitmap;
	if (XAllocColor( dpy, cmap, &terracotta )) { /* we got what we want */
		gcvalues.foreground = terracotta.pixel;
		terraGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground,
			&gcvalues);
		darkterraGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFillStyle | GCStipple,
			&gcvalues);
	} else if (darkgrey.pixel != grey.pixel) { /* fake it with darkgrey */
		terracotta.pixel = darkgrey.pixel;
		terraGC = darkgreyGC;
		gcvalues.foreground = darkgrey.pixel;
		darkterraGC = XCreateGC(dpy, XtWindow(frontpanel),
			GCForeground | GCBackground | GCFillStyle | GCStipple,
			&gcvalues);
	} else { /* fake it with grey; give up on gold/terra difference */
		terracotta.pixel = grey.pixel;
		terraGC = greyGC;
		darkterraGC = darkgreyGC;
	}

	/*
	 * Setup to handle events
	 */

	XtAddEventHandler(frontpanel, ButtonPressMask, FALSE,
			  handle_button_press, NULL);
	XtAddEventHandler(frontpanel, ButtonReleaseMask, FALSE,
			  handle_button_release, NULL);
	XtAddEventHandler(frontpanel, LeaveWindowMask, FALSE,
			  handle_button_release, NULL);
	XtAddEventHandler(frontpanel, KeyPressMask, FALSE,
			  handle_key_press, NULL);
	XtAddEventHandler(frontpanel, ExposureMask, FALSE,
			  handle_exposure, NULL);

} 

/**********************************************************/
/* Interface between cpu implementation and control panel */
/**********************************************************/

static void hitbreak() /* called by keyboard server to get attention */
{
	/* do nothing!  this was useful on the dumb-terminal
	   version, but is not needed on a window-based system */
}

kc8power(argc,argv) /* power-on initialize */
unsigned int argc;
char **argv;
{
	init_timer( console_delay );
	init_timer( halt_delay );
	schedule( &console_delay, console_interval, console_event, 0 );
	ttybreak = hitbreak;
	startwindow( argc, argv );
	makeboxes();
}

kc8init() /* console reset */
{
	/* nothing to do here */
}

kc8halt() /* respond to halt instruction */
{
	/* force console event! */
	schedule( &halt_delay, 0L, console_event, 0 );
}
xxxxxxxxxx
cat > kc8m.c <<\xxxxxxxxxx
/* File: kc8m.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PC8/M (blank and ugly tty based) control panel emulator.

   Based on the description in the PDP-8/E Maintenance manual,
   Digital Equipment Corporation, 1971, liberally interpreted.

*/

#include <stdio.h>
#include <strings.h>
#include "realtime.h"
#include "bus.h"

#define control(ch) (ch & 037)

/*********************/
/* utility functions */
/*********************/

ttyoctal( num, digits, suffix )
int num;
int digits;
char * suffix;
{
	char buf[32];
	int temp = num;
	int i = digits;
	while (i > 0) {
		i--;
		buf[i] = (temp & 07) | '0';
		temp >>= 3;
	}
	i = digits;
	if (suffix != (char *)0) {
		int j = 0;
		while (suffix[j] != '\0') {
			buf[i++] = suffix[j++];
		}
	}
	buf[i] = '\0';
	ttyputs(buf);
}


/**********************************************/
/* Implementation of device control interface */
/**********************************************/

struct device_rec {
	int (* mount)(); /* hook to mount file on device */
	void (* dismount)(); /* hook to dismount mounted file */
	int unit; /* thing to pass as param to mount and dismount */
	char * name; /* device name */
	char * longname; /* descriptive device name */
	char * file; /* file attached to device */
	struct device_rec * next; /* next list element */
};
static struct device_rec * devices = NULL;

register_device(m,d,u,n,l,f)
int (* m)(); /* hook to mount file on device */
void (* d)(); /* hook to dismount mounted file */
int u; /* device unit */
char * n; /* device name */
char * l; /* descriptive device name */
char * f; /* file attached to device */
{
	struct device_rec * temp;
	temp = (struct device_rec *)malloc( sizeof( struct device_rec ) );
	temp -> next = devices;
	temp -> mount = m;
	temp -> dismount = d;
	temp -> unit = u;
	temp -> name = n;
	temp -> longname = l;
	temp -> file = f;
	devices = temp;
}

close_devices()
{
	struct device_rec * temp = devices;
	while (temp != NULL) {
		(* (temp -> dismount))( temp -> unit );
		temp = temp -> next;
	}
}

static list_devices()
{
	struct device_rec * temp = devices;
	ttyputs( "\r\n" );
	while (temp != NULL) {
		ttyputs( temp -> name );
		ttyputs( temp -> longname );
		ttyputs( temp -> file );
		ttyputs( "\r\n" );
		temp = temp -> next;
	}
}

static struct device_rec * get_device(n)
char * n;
{
	struct device_rec * temp = devices;
	while ((temp != NULL) && (strcmp(temp -> name, n) != 0)) {
		temp = temp -> next;
	}
	return temp;
}

/********************************************/
/* Implementation of control panel function */
/********************************************/

static struct timer console_delay;

extern void (* ttybreak) (); /* hook to tty for keyboard overrun */

static char * help_message = "\
\r\n\
PDP-8/E emulator, commands are:\r\n\
\n\
 nnnnnG = goto location N  nnnnn/ = open memory location N\r\n\
 C = continue from halt    A = open accumulator\r\n\
 L = open link             S = open switch register\r\n\
 <cr> = close unchanged    nnnn<cr> = store N in open location, then close\r\n\
 the value in a location is printed when that location is opened.\r\n\
\n\
 D = list all devices\r\n\
 M = mount file on device\r\n\
 to dismount file, mount nothing on the device\r\n\
\n";

static void console_event()
/* all console functions happen here! */
{
	int number = -1; /* number from command line */
	int loc = 0; /* current memory location (negative => special loc) */
	char ch; /* input character */

#ifdef KM8E
	ttyoctal( ifr >> 12, 1, "" );
#endif
	ttyoctal( pc, 4, " (" );
	ttyoctal( ac, 4, "\r\n" );
	do {
		do { /* get next character but ignore break characters */
			ch = ttygetc();
		} while (ch == control('c'));
		ttyputc( ch );
		if ((ch >= '0')&&(ch <= '7')) {
			if (number < 0) {
				number = 0;
			}
			number = (number << 3) | (ch & 07);
			if (number >= MAXMEM) {
				number = -1;
				ttyputs( "?\r\n" );
			}
		} else switch (ch) {
		case '/': /* open number*/
			if (number >= 0) {
				loc = number;
				number = -1;
			}
			ttyoctal( memory[loc], 4, " " );
			break;
		case 'S':
		case 's': /* open switch register */
			loc = -3;
			number = -1;
			ttyoctal( sr, 4, " " );
			break;
		case 'A':
		case 'a': /* open accumulator */
			loc = -2;
			number = -1;
			ttyoctal( ac, 4, " " );
			break;
		case 'L':
		case 'l': /* open link */
			loc = -1;
			number = -1;
			ttyoctal( (link >> 12), 1, " " );
			break;
		case '\r': /* set open location and close */
		case '\n': /* synonym */
			if (number >= 0) {
				if (loc >= 0) {
					memory[loc] = number & 07777;
				} else if (loc == -3) {
					sr = number & 07777;
				} else if (loc == -2) {
					ac = number & 07777;
				} else if (loc == -1) {
					link = (number & 01) << 12;
				}
				number = -1;
			}
			if (ch == '\r') {
				ttyputc( '\n' );
			} else {
				ttyputc( '\r' );
			}
			break;
		case 'G':
		case 'g': /* Go */
			clearflags();
			if (number >= 0) {
				pc = number & 07777;
#ifdef KM8E
				ifr = number & 070000;
#endif
			} else {
				pc = 0;
#ifdef KM8E
				ifr = 0;
#endif
			}
#ifdef KM8E
			ib = ifr;
			dfr = ifr;
			cpma = pc | ifr;
#else
			cpma = pc;
#endif
			ttyputs( "\r\n" );
			run = 1;
			break;
		case 'C':
		case 'c': /* Continue */
			ttyputs( "\r\n" );
			run = 1;
			break;
		case 'Q':
		case 'q': /* Quit */
			ttyputs( "\r\n" );
			powerdown();
			/* the above never returns */
		case 'D':
		case 'd': /* List Devices */
			list_devices();
			break;
		case 'M':
		case 'm': /* Mount file on device */
			{
				struct device_rec * d;
				char n[5];
				char f[80];
				ttyputs( "\r\n device: " );
				ttygets( n, 5 );
				if ((d = get_device(n)) != NULL) {
					int u = d -> unit;
					ttyputs( " file: " );
					ttygets( f, 80 );
					if (f[0] != '\0') {
						if (!(*(d -> mount))( u, f )) {
							ttyputs( "?\r\n" );
						}
					} else {
						(* (d -> dismount))( u );
					}
				} else {
					ttyputs( "?\r\n" );
				}
			}
			break;
		case '?':
			ttyputs( help_message );
			number = -1;
			break;
#ifdef DEBUG
		case 'H':
		case 'h':
			ttyputs( "\r\n" );
			output_debug();
			break;
#endif
		default:
			ttyputs( "?\r\n" );
			number = -1;
		}
	} while ( run == 0 );

	/* note that console mode is still raw on exit */
}

/**********************************************************/
/* Interface between cpu implementation and control panel */
/**********************************************************/

static void hitbreak() /* called by keyboard server to get attention */
{
	run = 0;
	schedule( &console_delay, 0L, console_event, 0 );
}

kc8power(argc,argv) /* power-on initialize */
int argc;
char** argv;
{
	init_timer( console_delay );
	ttybreak = hitbreak;
	ttyputs( "PDP-8 Emulator, type ? for help.\r\n" );
}

kc8init() /* console reset */
{
	/* nothing to do here */
}

kc8halt() /* respond to halt instruction */
{
	/* force console event! */
	schedule( &console_delay, 0L, console_event, 0 );
}

xxxxxxxxxx
cat > kk8e.c <<\xxxxxxxxxx
/* File: kk8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PDP-8/e emulator

   Based on the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.
*/

/* First, declare that this is a main program */
#define MAIN

#include <stdio.h>
#include "bus.h"
#include "realtime.h"


/************************************************************/
/* Declarations of machine components not included in bus.h */
/************************************************************/

/* Machine control bits */

int enab_del; /* secondary enable flipflop, used to delay enable operations */


/******************/
/* Initialization */
/******************/

/* Both the reset key on the console and the CAF instruction call this */
clearflags()
{
	/* device specific effects of the reset operation */
	kc8init(); /* front panel */
#ifdef DEBUG
	reset_debug();
#endif
#ifdef KE8E
	ke8einit(); /* eae */
#endif
#ifdef KM8E
	km8einit(); /* mmu */
#endif
#ifdef DK8E
	dk8einit(); /* real-time clock */
#endif
#ifdef KP8E
	kp8einit(); /* power fail */
#endif
#ifdef KL8E
	kl8einit(); /* console TTY */
#endif
#ifdef PC8E
	pc8einit(); /* paper tape reader punch */
#endif
#ifdef RX8E
	rx8einit(); /* diskette drive */
#endif
	link = 000000;
	ac = 00000;
	irq = 0;
	enab = 0;
	enab_del = 0;
}

static closecore(u)
int u;
{
	corename[0] = '\0';
}

static int opencore(u, f)
int u;
char * f;
{
	set_file_name( corename, f );
	return(1);
}

powerup(argc,argv)
/* called only once for power on */
int argc;
char** argv;
{
	/* first, see if there is a specified core image save file,
	   since PDP-8/E machines usually have core memory and tend
	   to remember what was in them as of the previous shutdown

	   A parameter of the form <filename> with no leading dash
	   is interpreted as the name of the core file.  If there is
	   no core file specified, core comes up uninitialized.
	*/

	run = 0; /* by default, the machine comes up halted */

	getargs( argc, argv );

	if (corename[0] != '\0') { /* there is a core file */
		readcore(corename);
		/* if the machine was running when last stopped, this may
		   set the run flipflop */
	}
        register_device( opencore, closecore, 0,
                         "CORE", "- saved image of main memory   ",
                         corename );

	/* take over the TTY */
	ttyraw();

	/* initialize the real-time simulation mechanisms */
	init_timers();

	/* initialize all devices to their power-on state */
	kc8power( argc, argv ); /* console */
#ifdef KE8E
	ke8epower(); /* eae */
#endif
#ifdef KE8E
	km8epower(); /* mmu */
#endif
#ifdef DK8E
        dk8epower(); /* real-time clock */
#endif
#ifdef KP8E
	kp8epower(); /* power fail */
#endif
#ifdef KL8E
        kl8epower(); /* console TTY */
#endif
#ifdef PC8E
        pc8epower(); /* paper tape reader punch */
#endif
#ifdef RX8E
	rx8epower(); /* diskette drive */
#endif

	clearflags();
}

powerdown()
/* called only once from the console to exit the emulator */
{
	ttyrestore();
	if (corename[0] != '\0') { /* there is a core file */
		dumpcore(corename);
	}
	close_devices();
	exit(0);
}


/************************/
/* Instruction decoding */
/************************/

/*
/			       _____ _____ _____ _____ 
/  instruction word format: = |_|_|_|_|_|_|_|_|_|_|_|_|
/ 			      | op  |i|z|     adr     |
/
/  Instructions will be decoded using a 5 bit opcode-mode combination,
/  so the following opcode definitions are shifted 2 places left
*/

#define opAND (0 << 2)
#define opTAD (1 << 2)
#define opISZ (2 << 2)
#define opDCA (3 << 2)
#define opJMS (4 << 2)
#define opJMP (5 << 2)
#define opIOT (6 << 2)
#define opOPR (7 << 2)

/* The following definitions give the addressing modes as a 2 bit field
*/

#define DIRECT 0
#define DEFER 2
#define ZERO 0
#define CURRENT 1

/* The following definitions give instruction times in terms of 200 ns ticks
*/
#define shortcycle 6
#define longcycle 7

/* The following definitions give widely used code for addressing
*/

#ifdef KM8E

#define PAGE_ZERO cpma = ((mb & 0177) | ifr)

#define CURRENT_PAGE cpma = ((mb & 0177) | (pc & 07600) | ifr)

#define DEFER_CYCLE {							\
	if ((cpma & 07770) == 00010) { /* indexed */			\
		cpma = ((memory[cpma] = ((memory[cpma] + 1) & 07777))) | dfr;\
		countdown -= longcycle;					\
	} else { /* normal */						\
		cpma = (memory[cpma]) | dfr;				\
		countdown -= shortcycle;				\
	}								\
}
#else

#define PAGE_ZERO cpma = (mb & 0177)

#define CURRENT_PAGE cpma = (mb & 0177) | (pc & 07600)

#define DEFER_CYCLE {							\
	if ((cpma & 07770) == 00010) { /* indexed */			\
		cpma = (memory[cpma] = ((memory[cpma] + 1) & 07777));	\
		countdown -= longcycle;					\
	} else { /* normal */						\
		cpma = memory[cpma];					\
		countdown -= shortcycle;				\
	}								\
}

#endif


/* Emulate the fetch/execute cycle */
main(argc,argv)
int argc;
char **argv;
{
	powerup(argc,argv);
	if (run == 0) {
		kc8halt();
	}

	for (;;) {
		/* setup to fetch from pc */
#ifdef KM8E
		cpma = pc | ifr;
#else
		cpma = pc;
#endif
		/* I/O and console activity happens with CPMA holding PC */
		while (countdown <= 0) { /* handle pending device activity */
			fire_timer();
		}

		/* If an interrupt was requested, PC will change! */
		if ((irq > 0) && (enab_del != 0) && (enab_rtf != 0)) {
			/* an interrupt occurs */
			memory[0] = pc;
			pc = 1;
#ifdef KM8E
			sf = (ifr >> 9) | (dfr >> 12) | (uf << 6);
			ifr = 0;
			ib = 0;
			dfr = 0;
			uf = 0;
			ub = 0;
			cpma = pc | ifr;
#else
			cpma = pc;
#endif
			countdown -= longcycle;
			enab = 0;
		}

		/* this line handles 1 instr delay of interrupt enable */
		enab_del = enab;

		/* the actual instruction fetch is here */
		mb = memory[cpma];
#ifdef DEBUG
		accumulate_debug(cpma,mb);
#endif
		countdown -= shortcycle;

		switch (mb >> 7) { /* note that we decode i and z here */


		case opAND | DIRECT | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			ac = ac & memory[cpma];
			countdown -= longcycle;
			break;

		case opAND | DIRECT | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			ac = ac & memory[cpma];
			countdown -= longcycle;
			break;

		case opAND | DEFER | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			ac = ac & memory[cpma];
			countdown -= longcycle;
			break;

		case opAND | DEFER | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			ac = ac & memory[cpma];
			countdown -= longcycle;
			break;


		case opTAD | DIRECT | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			ac = (ac | link) + memory[cpma];
			link = ac & 010000;
			ac = ac & 007777;
			countdown -= longcycle;
			break;

		case opTAD | DIRECT | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			ac = (ac | link) + memory[cpma];
			link = ac & 010000;
			ac = ac & 007777;
			countdown -= longcycle;
			break;

		case opTAD | DEFER | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			ac = (ac | link) + memory[cpma];
			link = ac & 010000;
			ac = ac & 007777;
			countdown -= longcycle;
			break;

		case opTAD | DEFER | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			ac = (ac | link) + memory[cpma];
			link = ac & 010000;
			ac = ac & 007777;
			countdown -= longcycle;
			break;


		case opISZ | DIRECT | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			mb = memory[cpma] = ((memory[cpma] + 1) & 07777);
			if (mb == 0) {
				pc = (pc + 1) & 07777;
			}
			countdown -= longcycle;
			break;

		case opISZ | DIRECT | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			mb = memory[cpma] = ((memory[cpma] + 1) & 07777);
			if (mb == 0) {
				pc = (pc + 1) & 07777;
			}
			countdown -= longcycle;
			break;

		case opISZ | DEFER | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			mb = memory[cpma] = ((memory[cpma] + 1) & 07777);
			if (mb == 0) {
				pc = (pc + 1) & 07777;
			}
			countdown -= longcycle;
			break;

		case opISZ | DEFER | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			mb = memory[cpma] = ((memory[cpma] + 1) & 07777);
			if (mb == 0) {
				pc = (pc + 1) & 07777;
			}
			countdown -= longcycle;
			break;


		case opDCA | DIRECT | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			memory[cpma] = ac;
			ac = 00000;
			countdown -= longcycle;
			break;

		case opDCA | DIRECT | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			memory[cpma] = ac;
			ac = 00000;
			countdown -= longcycle;
			break;

		case opDCA | DEFER | ZERO:
			PAGE_ZERO;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			memory[cpma] = ac;
			ac = 00000;
			countdown -= longcycle;
			break;

		case opDCA | DEFER | CURRENT:
			CURRENT_PAGE;
			pc = (pc + 1) & 07777;
			DEFER_CYCLE;
			memory[cpma] = ac;
			ac = 00000;
			countdown -= longcycle;
			break;

/* force indirect branching to use the instruction field */
#define dfr ifr

		case opJMS | DIRECT | ZERO:
			PAGE_ZERO;
#ifdef KM8E
			ifr = ib;
			uf = ub;
#endif
			enab_rtf = 1;
			memory[cpma] = (pc + 1) & 07777;
			countdown -= longcycle;
			pc = (cpma + 1) & 07777;
			break;

		case opJMS | DIRECT | CURRENT:
			CURRENT_PAGE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			memory[cpma] = (pc + 1) & 07777;
			countdown -= longcycle;
			pc = (cpma + 1) & 07777;
			break;

		case opJMS | DEFER | ZERO:
			PAGE_ZERO;
			DEFER_CYCLE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			memory[cpma] = (pc + 1) & 07777;
			countdown -= longcycle;
			pc = (cpma + 1) & 07777;
			break;

		case opJMS | DEFER | CURRENT:
			CURRENT_PAGE;
			DEFER_CYCLE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			memory[cpma] = (pc + 1) & 07777;
			countdown -= longcycle;
			pc = (cpma + 1) & 07777;
			break;


		case opJMP | DIRECT | ZERO:
			PAGE_ZERO;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			pc = cpma & 07777;
			break;

		case opJMP | DIRECT | CURRENT:
			CURRENT_PAGE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			pc = cpma & 07777;
			break;

		case opJMP | DEFER | ZERO:
			PAGE_ZERO;
			DEFER_CYCLE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			pc = cpma & 07777;
			break;

		case opJMP | DEFER | CURRENT:
			CURRENT_PAGE;
			DEFER_CYCLE;
#ifdef KM8E
			ifr = ib;
			uf = ub;
			cpma = (cpma & 07777) | ifr;
#endif
			enab_rtf = 1;
			pc = cpma & 07777;
			break;

/* undo kluge to force branches to use instruction field */
#undef dfr

		case opIOT | DIRECT | ZERO:
		case opIOT | DIRECT | CURRENT:
		case opIOT | DEFER | ZERO:
		case opIOT | DEFER | CURRENT:

			pc = (pc + 1) & 07777;
#ifdef KM8E
			if (uf == 1) { /* illegal in user mode */
				irq = irq + 1;
				km8e_uif = 1;
				break; /* abort instruction */
			}
#endif
			switch ((mb >> 3) & 077) { /* decode device address */

			case 000:
				switch (mb & 07) { /* decode CPU IOTs */
				case 00: /* SKON */
					if (enab != 0) {
						pc = (pc + 1) & 07777;
					}
					enab = 0;
					enab_del = 0;
					break;
				case 01: /* ION */
					enab = 1;
					break;
				case 02: /* IOF */
					enab = 0;
					enab_del = 0;
					break;
				case 03: /* SRQ */
					if (irq > 0) {
						pc = (pc + 1) & 07777;
					}
					break;
				case 04: /* GTF */
					ac = (link >> 1)       /* bit 0 */
#ifdef KE8E
					   | (gt?)	       /* bit 1 */
#endif
					   | ((irq > 0) << 9)  /* bit 2 */
#ifdef KM8E
					   | (0)	/*?*/  /* bit 3 */
#endif
					   | (enab << 7)       /* bit 4 */
#ifdef KM8E
					   | sf 	       /* bit 5-11 */
#endif
					;
					break;
				case 05: /* RTF */
					link = (ac<<1)& 010000; /* bit 0 */
#ifdef KE8E
					gt = ?			/* bit 1 */
#endif
					/* nothing */		/* bit 2 */
					/* nothing */		/* bit 3 */
					enab = 1;		/* bit 4 */
#ifdef KM8E
					ub = (ac & 00100) >> 6; /* bit 5 */
					ib = (ac & 00070) << 9; /* bit 6-8 */
					dfr = (ac & 00007) << 12;/* bit 9-11 */
#endif
					/* disable interrupts until branch */
					enab_rtf = 0;
					break;
				case 06: /* SGT */
#ifdef KE8E
					if (?) {
						pc = (pc + 1) & 07777;
					}
#endif
					break;
				case 07: /* CAF */
					clearflags();
					break;
				}
				break;

#ifdef PC8E
			case 001:
				pc8edev1(mb & 07);
				break;
			case 002:
				pc8edev2(mb & 07);
				break;
#endif

#ifdef KL8E
			case 003:
				kl8edev3(mb & 07);
				break;
			case 004:
				kl8edev4(mb & 07);
				break;
#endif

#ifdef DK8E
			case 013:
				dk8edev(mb & 07);
				break;
#endif

#ifdef KM8E
			case 020:
			case 021:
			case 022:
			case 023:
			case 024:
			case 025:
			case 026:
			case 027:
				km8edev(mb & 077);
				break;
#endif

#ifdef RX8E
			case 075:
				rx8edev(mb & 07);
				break;
#endif

#ifdef TC08
			case 076:
				tc08dev6(mb & 07);
				break;
			case 077:
				tc08dev7(mb & 07);
				break;
#endif

			default: /* non existant device */
				break;
			}
			break;


		case opOPR | DIRECT | ZERO:	/* group 1, CLA = 0 */
		case opOPR | DIRECT | CURRENT:	/* group 1, CLA = 1 */

			pc = (pc + 1) & 07777;
			switch ((mb >> 4) & 017) { /* decode CLA ... CML here */

			case 000:	/* NOP */
				break;

			case 001:	/*             CML */
				link = link ^ 010000; 
				break;

			case 002:	/*         CMA     */
				ac = ac ^ 007777;
				break;

			case 003:	/*         CMA CML */
				ac = ac ^ 007777;
				link = link ^ 010000; 
				break;

			case 004:	/*     CLL         */
				link = 000000; 
				break;

			case 005:	/*     CLL     CML */
				link = 010000; 
				break;

			case 006:	/*     CLL CMA     */
				ac = ac ^ 007777;
				link = 000000; 
				break;

			case 007:	/*     CLL CMA CML */
				ac = ac ^ 007777;
				link = 010000; 
				break;

			case 010:	/* CLA             */
				ac = 00000;
				break;

			case 011:	/* CLA         CML */
				ac = 00000;
				link = link ^ 010000; 
				break;

			case 012:	/* CLA     CMA     */
				ac = 07777;
				break;

			case 013:	/* CLA     CMA CML */
				ac = 07777;
				link = link ^ 010000; 
				break;

			case 014:	/* CLA CLL         */
				ac = 00000;
				link = 000000;
				break;

			case 015:	/* CLA CLL     CML */
				ac = 00000;
				link = 010000;
				break;

			case 016:	/* CLA CLL CMA     */
				ac = 07777;
				link = 000000;
				break;

			case 017:	/* CLA CLL CMA CML */
				ac = 07777;
				link = 010000;
				break;
			}

			if (mb & 00001) { /* IAC */
				ac = (ac | link) + 1;
				link = ac & 010000;
				ac = ac & 007777;
			}

			switch ((mb >> 1) & 07) { /* decode RAR,RAL,TWO */

			case 00:	/* NOP */
				break;

			case 01:	/*         TWO -- BSW */
				ac = ((ac & 07700) >> 6)
				   | ((ac & 00077) << 6);
				break;

			case 02:	/*     RAL     */
				ac = (ac << 1) | (link >> 12);
				link = ac & 010000;
				ac = ac & 007777;
				break;

			case 03:	/*     RAL TWO */
				ac = (ac << 2) | ((ac | link) >> 11);
				link = ac & 010000;
				ac = ac & 007777;
				break;

			case 04:	/* RAR         */
				ac = ((ac | link) >> 1) | (ac << 12);
				link = ac & 010000;
				ac = ac & 007777;
				break;

			case 05:	/* RAR     TWO */
				ac = ((ac | link) >> 2) | (ac << 11);
				link = ac & 010000;
				ac = ac & 007777;
				break;

			case 06:	/* RAR RAL     */
				/* this uses a data path meant for AND */
				ac = ac & mb;
				break;

			case 07:	/* RAR RAL TWO */
				/* this uses an addressing data path */
				ac = ((pc - 1) & 07600) | (mb & 00177);
				break;

			}

			break;

		case opOPR | DEFER | ZERO:	/* group 2,3 CLA = 0 */
		case opOPR | DEFER | CURRENT:   /* group 2,3 CLA = 1 */

			if ((mb & 00001) == 0) { /* GROUP 2 */

				pc = (pc + 1) & 07777;

				switch ((mb & 00170) >> 3) { /* SMA ... REV */

				case 000: /* NOP */
					break;

				case 001: /*             REV */
					pc = (pc + 1) & 07777;
					break;

				case 002: /*         SNL     */
					if (link) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 003: /*         SNL REV */
					if (link == 0) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 004: /*     SZA         */
					if (ac == 0) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 005: /*     SZA     REV */
					if (ac) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 006: /*     SZA SNL     */
					if ((ac == 0) || link) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 007: /*     SZA SNL REV */
					if (ac && (link == 0)) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 010: /* SMA             */	
					if (ac & 04000) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 011: /* SMA         REV */
					if ((ac & 04000) == 0) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 012: /* SMA     SNL     */
					if ((ac | link) & 014000) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 013: /* SMA     SNL REV */	
					if (((ac | link) & 014000) == 0) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 014: /* SMA SZA         */
					if ((ac & 04000) || (ac == 0)) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 015: /* SMA SZA     REV */
					if (((ac & 004000) == 0) && ac) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 016: /* SMA SZA SNL     */
					if (((ac | link) & 014000)
					 || (ac == 0)) {
						pc = (pc + 1) & 07777;
					}
					break;

				case 017: /* SMA SZA SNL REV */	
					if ((((ac | link) & 014000) == 0)
					 && (ac)) {
						pc = (pc + 1) & 07777;
					}
					break;
				}

				if (mb & 00200) { /* CLA */
					ac = 00000;
				}
#ifdef KM8E
				if ((uf != 0) && ((mb & 00006) != 0)) {
					/* illegal in user mode */
					irq = irq + 1;
					km8e_uif = 1;
					break; /* abort instruction */
				}
#endif
				if (mb & 00004) { /* OSR */
					ac = ac | sr;
				}

				if (mb & 00002) { /* HLT */
					kc8halt();
					countdown = 0;
					run = 0;
				}

			} else { /* GROUP 3 */
				pc = (pc + 1) & 07777;

				if (mb & 00200) { /* CLA */
					ac = 00000;
				}

				if ((mb & 00120) == 00100) { /* MQA */
					ac = mq | ac;
				} else if ((mb & 00120) == 00020) { /* MQL */
					mq = ac;
					ac = 00000;
				} else if ((mb & 00120) == 00120) { /*MQA,MQL*/
					int temp;
					temp = mq;
					mq = ac;
					ac = temp;
				}
#ifdef KE8E
				if ((mb & 00400) == 0) { /* CLASS 1 */
					switch ((mb & 00016) >> 1) {
					case 00: /* NOP */
						break;
					case 01: /* ACS */
						sc = ac & 037;
						break;
					case 02: /* */
						break;
					case 03: /* */
						break;
					case 04: /* */
						break;
					case 05: /* SAM */
						ac = mq - ac;
						break;
					case 06: /* */
						break;
					case 07: /* */
						break;
					}
				} else { /* CLASS 2 */
					switch ((mb & 00016) >> 1) {
					case 00: /* SCA */
						ac = ac | sc;
						break;
					case 01: /* */
						break;
					case 02: /* */
						break;
					case 03: /* */
						break;
					case 04: /* */
						break;
					case 05: /* */
						break;
					case 06: /* */
						break;
					case 07: /* */
						break;
					}
				}
#endif
			}
		}
	}
}
xxxxxxxxxx
cat > kl8e.c <<\xxxxxxxxxx
/* File: kl8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC KL8/E console teletype emulator, for the console device only!
            Emulators for other asynchronous interfaces must be constructed
            separately.

   Based on the description in the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.

   Uses nonblocking polling to grab characters from the keyboard.  This is
   a bit too synchronous a style of reading, but until ttyaccess.c can be
   made to use interrupts, it will do.  As a result, this should look to
   software as if it is reading from a synchronous data line interface
   that automatically discards pad characters.  Users who type too fast
   will note that their input-overruns lead to lost data.
*/

#include <stdio.h>
#include "realtime.h"
#include "bus.h"

/*********************************************************************/
/* options:  The user may change the speed of the simulated teletype */
/* to any positive value.  DEC sold the KL8E in the following        */
/* standard versions with an RS232 interface:                        */
/*                                                                   */
/*   KL8E option:           A     B     C     D     E     F     G    */
/*   M8650 version:         -     YA    YA    YA    YA    YA    YA   */
/*   Transmit baud rate:   110   150   300   600  1200  1200  2400   */
/*   Receive baud rate:    110   150   300   600  1200   150   150   */
/*   Receive baud rate:    110   150   300   600  1200   150   150   */
/*                                                                   */
/* In fact, the M8650 YA board could be jumpered to handle 2400,     */
/* 4800 and 9600 baud on both input and output, but DEC didn't tell  */
/* people about this, at least not in the manual.                    */
/*********************************************************************/

/* translations from baud rate to time per character */

#define baud110  (100    * millisecond)
#define baud150  ( 66667 * microsecond)
#define baud300  ( 33333 * microsecond)
#define baud600  ( 16667 * microsecond)
#define baud1200 (  8333 * microsecond)
#define baud2400 (  4166 * microsecond)
#define baud4800 (  2083 * microsecond)
#define baud9600 (  1041 * microsecond)

/* select the baud rate here (note: if the emulator runs at 1/10 the speed
   of the real PDP-8, 1200 baud simulation will look like 110 to a person! */

#define print_time (baud110 / IOFUDGE)
#define read_time (baud110 / IOFUDGE)


/****************************************************************/
/* restrictions:  This version of the console has no low-speed  */
/* paper-tape reader or punch -- call it a KSR teletype instead */
/* of the usual ASR teletype.                                   */
/****************************************************************/


/*********************************************************/
/* Interface between device implementation and "console" */
/*********************************************************/

/* timers used to simulate delays between I/O initiation and completion */
static struct timer print_delay;
static struct timer read_delay;


/*************************************/
/* "officially visible" device state */
/*************************************/

static int keyboard_flag;
static int keyboard_buffer;
static int interrupt_enable;
static int print_flag;
static int print_buffer;


/*************************/
/* Device Implementation */
/*************************/

static void keyboard_event(p) /* called to poll for keyboard input */
int p;
{
	int poll;

	if ((poll = ttypoll()) >= 0) { /* a key was pressed */
		/* Follow the DEC convention of setting the high bit */
		keyboard_buffer = poll | 0200;

		/* report the keypress to the rest of the emulator */
		if (keyboard_flag != 1) { /* no overrun condition */
			keyboard_flag = 1;
			if (interrupt_enable == 1) {
				irq = irq + 1;
			}
		}
	}

	schedule( &read_delay, read_time, keyboard_event, 0 );
}

static read_character()
{ /* called to enable keyboard_event when reading from tape */
	/* right now, tape is not supported, so this does nothing */
}

static void print_event(p)
int p;
{ /* called from timer when a byte has been successfully printed */
	/* this code allows for the DEC convention of setting the high bit */
	ttyputc( print_buffer & 0177 );
	print_flag = 1;
	if (interrupt_enable == 1) {
		irq = irq + 1;
	}
}

static print_character()
{ /* schedule the completion of a print "print_time" in the future */
	schedule( &print_delay, print_time, print_event, 0 );
}


/******************/
/* Initialization */
/******************/

kl8epower() /* global initialize */
{
	/* set up timers used to delay I/O activity */
        init_timer(print_delay);
        init_timer(read_delay);

	/* the following makes the reader run forever (probably wrong) */
	schedule( &read_delay, read_time, keyboard_event, 0 );
}


kl8einit() /* console reset */
{
	keyboard_flag = 0;
	print_flag = 0;
	interrupt_enable = 1;
}

/********************/
/* IOT Instructions */
/********************/

kl8edev3(op)
int op;
{
	switch (op) {
	case 00: /* KCF */
		if (interrupt_enable == 1) {
			irq = irq - keyboard_flag;
		}
		keyboard_flag = 0;
		break;
	case 01: /* KSF */
		if (keyboard_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		break;
	case 02: /* KCC */
		if (interrupt_enable == 1) {
			irq = irq - keyboard_flag;
		}
		keyboard_flag = 0;
		ac = 00000;
		read_character();
		break;
	case 03: /* no operation! */
		break;
	case 04: /* KRS */
		ac = ac | keyboard_buffer;
		break;
	case 05: /* KIE != KSF KRS */
		if ((ac & 00001) == 0) { /* disable interrupts */
			if (interrupt_enable == 1) {
				interrupt_enable = 0;
				irq = irq - (keyboard_flag + print_flag);
			}
		} else { /* enable interrupts */
			if (interrupt_enable == 0) {
				interrupt_enable = 1;
				irq = irq + (keyboard_flag + print_flag);
			}
		}
		break;
	case 06: /* KRB = KCC KRS */
		if (interrupt_enable == 1) {
			irq = irq - keyboard_flag;
		}
		keyboard_flag = 0;
		ac = keyboard_buffer;
		read_character();
		break;
	case 07: /* no operation! */
		break;
	}
}

kl8edev4(op)
int op;
{
	switch (op) {
	case 00: /* TFL */
		if (print_flag == 0) {
			print_flag = 1;
			if (interrupt_enable == 1) {
				irq = irq + 1;
			}
		}
		break;
	case 01: /* TSF */
		if (print_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		break;
	case 02: /* TCF */
		if (interrupt_enable == 1) {
			irq = irq - print_flag;
		}
		print_flag = 0;
		break;
	case 03: /* no operation! */
		break;
	case 04: /* TPC */
		print_buffer = ac & 00377;
		print_character();
		break;
	case 05: /* TSK != TSF TPC */
		if ((print_flag == 1) || (keyboard_flag == 1)) {
			pc = (pc + 1) & 07777;
		}
		break;
	case 06: /* TLS = TCF TPC */
		if (interrupt_enable == 1) {
			irq = irq - print_flag;
		}
		print_flag = 0;
		print_buffer = ac & 00377;
		print_character();
		break;
	case 07: /* no operation! */
		break;
	}
}
xxxxxxxxxx
cat > km8e.c <<\xxxxxxxxxx
/* File: km8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC KM8/E memory management functions, excepting those
            included in the KK8E cpu.  This version does not support
	    the time-share option.

   Based on the description in the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.
*/

#include "bus.h"

/******************/
/* Initialization */
/******************/

km8epower() /* global initialize */
{
	ifr = 0;
        ib = 0;
        dfr = 0;
	sf = 0;
	uf = 0;
	ub = 0;
}


km8einit() /* console reset */
{
	km8e_uif = 0;
	/* reset to user mode eventually */
}

/********************/
/* IOT Instructions */
/********************/

km8edev(op)
int op;
{
	switch (op & 07) {
	case 00:
		break;
	case 01: /* CDF */
		dfr = (op & 0070) << 9;
		break;
	case 02: /* CIF */
		ib = (op & 0070) << 9;
		enab_rtf = 0; /* disable interrupt till branch */
		break;
	case 03: /* CDF CIF */
		ib = (op & 0070) << 9;
		dfr = ib;
		enab_rtf = 0; /* disable interrupt till branch */
		break;
	case 04: /* other */
		switch (op >> 3) {
		case 00: /* CINT */
			/* clear user interrupt flag */
			irq = irq - km8e_uif;
			km8e_uif = 0;
			break;
		case 01: /* RDF */
			ac = ac | (dfr >> 9);
			break;
		case 02: /* RIF */
			ac = ac | (ifr >> 9);
			break;
		case 03: /* RIB */
			ac = ac | sf;
			break;
		case 04: /* RMF */
			/* user state bit ? */
			ib = (sf & 070) << 9;
			dfr = (sf & 007) << 12;
			ub = (sf & 0100) >> 6;
			enab_rtf = 0; /* disable interrupt till branch */
			break;
		case 05: /* SINT */
			/* skip if user interrupt flag */
			if (km8e_uif == 1) {
				pc = (pc + 1) & 07777;
			}
			break;
		case 06: /* CUF */
			/* clear user flag */
			ub = 0;
			break;
		case 07: /* SUF */
			/* set user flag, */
			ub = 1;
			enab_rtf = 0; /* disable interrupt till branch */
			break;
		}
		break;
	case 05:
	case 06:
	case 07:
		/* no op? */
		break;
	}
}
xxxxxxxxxx
cat > pc8e.c <<\xxxxxxxxxx
/* File: pc8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PC8/E high speed paper tape reader-punch emulator,

   Based on the description in the PDP-8/E Small Computer Handbook,
   Digital Equipment Corporation, 1971.

*/

#include "realtime.h"
#include "bus.h"
#include <stdio.h>


/***********/
/* options */
/***********/

/* The advertised speed of the punch is 50 chars/sec. */
#define punch_time ((20 * millisecond) / IOFUDGE)

/* The advertised speed of the reader is 300 chars/sec. */
#define reader_time ((3333 * microsecond) / IOFUDGE)


/*********************************************************/
/* Interface between device implementation and "console" */
/*********************************************************/

/* files used to simulate the device */
static FILE *punch_stream;
static char punchname[NAME_LENGTH];
static FILE *reader_stream;
static char readername[NAME_LENGTH];

/* timers used to simulate delays between I/O initiation and completion */
static struct timer punch_delay;
static struct timer reader_delay;

static readerclose(u)
int u;
{
	if (reader_stream != NULL) {
		fclose( reader_stream );
		reader_stream = NULL;
		readername[0] = '\0';
	}
}

static int readeropen(u, f)
int u;
char * f;
{
	readerclose(u);
	set_file_name( readername, f );
	if ((reader_stream = fopen( readername, "r" )) == NULL) {
		readername[0] = '\0';
	}
	return (reader_stream != NULL);
}

static punchclose(u)
int u;
{
	if (punch_stream != NULL) {
		fclose( punch_stream );
		punch_stream = NULL;
		punchname[0] = '\0';
	}
}

static int punchopen(u, f)
int u;
char * f;
{
	punchclose(u);
	set_file_name( punchname, f );
	if ((punch_stream = fopen( punchname, "w" )) == NULL) {
		punchname[0] = '\0';
	}
	return (punch_stream != NULL);
}

pc8epower() /* power-on initialize */
{
	punch_stream = NULL;
	reader_stream = NULL;
	punchname[0] = '\0';
	readername[0] = '\0';
	init_timer( punch_delay );
	init_timer( reader_delay );
	register_device( readeropen, readerclose, 0,
			 "PTR", "-- high-speed paper-tape reader ",
			 readername );
	register_device( punchopen, punchclose, 0,
			 "PTP", "-- high-speed paper-tape punch  ",
			 punchname );
}


/*************************************/
/* "officially visible" device state */
/*************************************/

static int punch_flag;
static int punch_buffer;
static int reader_flag;
static int reader_buffer;
static int interrupt_enable;

/*************************/
/* Device implementation */
/*************************/

static void reader_event(p)
int p;
{ /* called from timer when a byte has been successfully read */
	if (reader_stream != NULL) {
		reader_buffer = getc(reader_stream);
		if (reader_buffer == EOF) {
			/* simulate the tape running out in the reader */
			readerclose(0);
			reader_buffer = 0377;
		};
	} else {
		/* simulate reading with no tape mounted */
		reader_buffer = 0377; /* all holes */
	}
	reader_flag = 1; /* signal that read is complete */
	if (interrupt_enable == 1) {
		irq = irq + 1;
	}
}

static read_character()
{ /* schedule the completion of a read "reader_time" in the future */
	schedule( &reader_delay, reader_time, reader_event, 0 );
}


static void punch_event(p)
int p;
{ /* called from timer when a byte has been successfully printed */
	if (punch_stream != NULL) {
		putc(punch_buffer, punch_stream);
	} else {
		/* simulate punching with no tape in punch */
	}
	punch_flag = 1;
	if (interrupt_enable == 1) {
		irq = irq + 1;
	}
}

static punch_character()
{ /* schedule the completion of a punch "punch_time" in the future */
	schedule( &punch_delay, punch_time, punch_event, 0 );
}



/***********************************************/
/* Initialization used by CAF and reset switch */
/***********************************************/

pc8einit() /* console reset */
{
	punch_flag = 0;
	reader_flag = 0;
	interrupt_enable = 1;
	/* assume that cpu clears irq for us */
}

/********************/
/* IOT Instructions */
/********************/

pc8edev1(op)
int op;
{
	switch (op) {
	case 00: /* RPE */
		if (interrupt_enable == 0) {
			interrupt_enable = 1;
			irq = irq + reader_flag + punch_flag;
		}
		break;
	case 01: /* RSF */
		if (reader_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		break;
	case 02: /* RRB */
		ac = ac | reader_buffer;
		break;
	case 03: /* no operation! */
		break;
	case 04: /* RFC */
		if (interrupt_enable == 1) {
			irq = irq - reader_flag;
		}
		reader_flag = 0;
		read_character();
		break;
	case 05: /* no operation! */
		break;
	case 06: /* RRB RFC */
		ac = ac | reader_buffer;
		if (interrupt_enable == 1) {
			irq = irq - reader_flag;
		}
		reader_flag = 0;
		read_character();
		break;
	case 07: /* no operation! */
		break;
	}
}

pc8edev2(op)
int op;
{
	switch (op) {
	case 00: /* PCE */
		if (interrupt_enable == 1) {
			interrupt_enable = 0;
			irq = irq - (reader_flag + punch_flag);
		}
		break;
	case 01: /* PSF */
		if (punch_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		break;
	case 02: /* PCF */
		if (interrupt_enable == 1) {
			irq = irq - punch_flag;
		}
		punch_flag = 0;
		break;
	case 03: /* no operation! */
		break;
	case 04: /* PPC */
		punch_buffer = ac & 00377;
		punch_character();
		break;
	case 05: /* no operation! */
		break;
	case 06: /* PCF PPC */
		if (interrupt_enable == 1) {
			irq = irq - punch_flag;
		}
		punch_flag = 0;
		punch_buffer = ac & 00377;
		punch_character();
		break;
	case 07: /* no operation! */
		break;
	}
}
xxxxxxxxxx
cat > realtime.c <<\xxxxxxxxxx
/* File: realtime.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: simulation core routines for real-time behavior of devices
            attached to instruction-set level computer emulators.
*/

#include "realtime.h"

/************************************************/
/* Read the comments in the include file first! */
/************************************************/

/**************************************************************************/
/* Timers that have been scheduled are stored in a linked list, ordered   */
/* by their delay from the present.  The delay in each timer record after */
/* the first is the delay between the preceeding event and that event.    */
/* The delay of the first scheduled timer is not recorded in that timer,  */
/* but is stored in countdown, which is periodically decremented.         */
/**************************************************************************/

#define niltimer (struct timer *)0

static struct timer * head; /* points to the next timer in the queue */

void init_timers()  /* initialize timer subsystem */
{
	head = niltimer; /* there is no pending timer initially */
	countdown = 0x7FFFFFFF; /* so put off next firing a long time */
}

void schedule(t,d,a,p)  /* schedule timer t after a delay of d ticks */
struct timer * t;
long int d;
void (* a)();
int p;
{
	t->action = a;
	t->param = p;
	if (t->delay >= 0) { /* timer is already scheduled */
		/* don't mess up the queue, schedule becomes no-op */
	} else if (head == niltimer) { /* nothing is scheduled yet */
		countdown = d;
		t->delay = d;
		t->next = niltimer;
		head = t;
	} else { /* there is an existing schedule */
		if (countdown > d) {    /* t goes before old head */
			head->delay = countdown - d;
			countdown = d;    /* set timer until t */
			t->next = head;   /* link t ahead of old head */
			t->delay = d;     /* mark timer as queued */
			head = t;	  /* reset list head */
		} else { /* new event goes down into queue */
			struct timer * i;
			i = head;
			d = d - countdown; /* delay relative to head */
			while (i->next != niltimer) { /* scan list for place */
				if (d < i->next->delay) {
					i->next->delay = i->next->delay - d;
					break;
				}
				i = i->next;
				d = d - i->delay; /* adjust delay */
			}
			t->next = i->next; /* link new timer into queue */
			t->delay = d;
			i->next = t;
		}
	}
}

void fire_timer()  /* cause timer to fire at current time */
{
	if (head == niltimer) { /* no pending timer */
		/* put off next firing as long as possible */
		countdown = 0x7FFFFFFF;
	} else {
		void (* a)();
		int p;

		/* save action for use after dequeue */
		a = head->action;
		p = head->param;

		/* mark head timer as idle */
		head->delay = -1;

		/* schedule next event */
		head = head->next;
		if (head == niltimer) { /* no new timer */
			/* put off next firing as long as possible */
			countdown = 0x7FFFFFFF;
		} else {
			/* figure delay till next timer */
			countdown = countdown + head->delay;
			/* note: this could have been countdown = head->delay,
			   except that countdown could have gone slightly
			   negative, and we want to make delays add nicely
			   for such devices as line frequency clocks */
		}

		/* fire action on head timer */
		(* a)(p);
	}
}
xxxxxxxxxx
cat > realtime.h <<\xxxxxxxxxx
/* realtime.h
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: interface to simulation core routines for real-time behavior
            of devices attached to instruction-set level computer emulators.
*/

/* The following trick puts extern on definitions if not in the main program */
#ifdef MAIN
#define EXTERN
#else
#define EXTERN extern
#endif


/**********************************************/
/* Times are measure in 200 nanosecond ticks. */
/**********************************************/

#define microsecond 5l
#define millisecond 5000l
#define second 5000000l

/**************************************************************************/
/* Using 32 bit signed integers, the maximum time is 2**31 - 1 ticks,     */
/* the conversion to seconds is (ns/tick)(sec/ns)(ticks) = sec,           */
/* so, a 32 bit clock can hold (200)(10**-9)(2**31-1) sec = 7.158 min     */
/*                                                                        */
/* This is not much running time, so scheduled events are always recorded */
/* as delays from the present, and no peripheral activity may be set to   */
/* happen more than 7 minutes into the future (this is plenty long for    */
/* such things as rewinding a reel of tape, one of the slowest actions.   */
/**************************************************************************/

/**************************************************************************/
/* For each device for which there may be asynchronous activity, a timer  */
/* needs to be allocated.  This timer is set whenever the activity is     */
/* caused, and when the timer expires, it calls the appropriate service   */
/* to simulate the activity in question                                   */
/**************************************************************************/

struct timer {
	long int delay;      /* the delay until the timer is to fire */
	void (* action)();   /* the function to call when the timer fires */
	int param;           /* parameter to action */
	struct timer * next; /* the next timer to worry about after this */
};

/* each timer must be initialized before the first time it is scheduled */
#define init_timer(t) t.delay = -1

void init_timers(); /* initialize entire timer subsystem */

void schedule(); /* schedule action a(p) on timer t after a delay of d */
/*           ( struct timer * t, long int d, void (* a)(), int p ) */

void fire_timer(); /* cause timer to fire at current time */

EXTERN int countdown; /* the delay until the next timer expiration */

/**************************************************************************/
/* It is up to the emulator to decrement countdown appropriately as each  */
/* machine instruction is interpreted, thus recording the passage of time */
/* The emulator is also responsible for calling fire_timer if countdown   */
/* ever reaches zero.                                                     */
/**************************************************************************/
xxxxxxxxxx
cat > rx8e.c <<\xxxxxxxxxx
/* File: rx8e.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC RX8 RX01 diskette interface

   Based on the description in the RX8/RX11 Floppy Disk System Maintenance Man.
   Digital Equipment Corporation, 1976.

   A simulated RX01 disk is a file containing the string "rx01" as the
   first 4 characters of the first 258 byte block.  The actual data
   sectors of the file occupy consecutive 128 byte blocks starting with
   byte 256.  Simulated RX01 diskettes should be created using rx01format.
*/

#include "realtime.h"
#include "bus.h"
#include <stdio.h>


/***********/
/* formats */
/***********/

/* RX01 physical diskette layout */
#define bytes_per_sector 128
#define min_sector 1
#define max_sector 26
#define sectors_per_track ((max_sector-min_sector)+1) /* 26 */
#define min_track 0
#define max_track 76
#define tracks_per_disk ((max_track-min_track)+1) /* 77 */
#define sectors_per_disk (tracks_per_disk * sectors_per_track) /* 2002 */
/* note that this implies that there are 256256 bytes per disk */

/* layout of file used to emulate diskette */
/* bytes 0 through 3 are "rx01" */
#define tag_delete 4
/* tag bytes 4 and up hold the bit-vector that marks deleted data sectors */
#define length_delete ((sectors_per_disk+7)/8) /* 251 */
#define tag_data (tag_delete+length_delete) /* 255 */
#define tag_sectors ((tag_data+bytes_per_sector-1)/bytes_per_sector) /* 2 */
/* round up to a multiple of the sector size for start of real data sectors */
#define tag_length (tag_sectors*bytes_per_sector) /* 256 */

/**********/
/* delays */
/**********/

/* RX8E interface to RX01 controller delay, per data chunk */
#define byte_time ((18 * microsecond) / IOFUDGE )
#define word_time ((23 * microsecond) / IOFUDGE )

/* RX01 seek times */
#define track_time ((10 * millisecond) / IOFUDGE )
#define settle_time ((25 * millisecond) / IOFUDGE )
#define rotate_time ((166 * millisecond) / IOFUDGE )
#define sector_time (rotate_time / sectors_per_track)


/*********************************************************/
/* Interface between device implementation and "console" */
/*********************************************************/

/* files used to simulate the device */
static FILE *disk[2];
static char diskname[2][NAME_LENGTH];
static char disktag[2][tag_length];
static int current_track[2];
static int current_sector[2];

/* timers used to simulate delays between I/O initiation and completion */
static struct timer rx8e_delay;
static struct timer rx01_delay;

static diskclose(u)
int u;
{
	if (disk[u] != NULL) {
		fseek( disk[u], 0L, 0 );
		fwrite( disktag[u], tag_length, 1, disk[u] );
		fclose( disk[u] );
		disk[u] = NULL;
		diskname[u][0] = '\0';
	}
}

static int diskopen(u, f)
int u;
char * f;
{
	diskclose(u);
	set_file_name( diskname[u], f );
	if ((disk[u] = fopen( diskname[u], "r+" )) == NULL) { /* can't open */
		diskname[u][0] = '\0';
	} else { /* the file exists, is it an RX01 disk? */
		if (fread( disktag[u], tag_length, 1, disk[u] ) == 1) {
			/* tag readable */
			if ((disktag[u][0] != 'r') || (disktag[u][1] != 'x')
			 || (disktag[u][2] != '0') || (disktag[u][3] != '1')) {
				/* bad header */
				diskclose(u);
			}
		} else { /* couldn't read tag */
			diskclose(u);
		};
	}
	current_track[u] = min_track;
	current_sector[u] = min_sector; /* should be randomized */
	return (disk[u] != NULL);
}

rx8epower() /* power-on initialize */
{
	disk[0] = NULL;
	disk[1] = NULL;
	diskname[0][0] = '\0';
	diskname[1][0] = '\0';
	init_timer( rx8e_delay );
	init_timer( rx01_delay );
	register_device( diskopen, diskclose, 1,
			 "RX1", "-- RX01 floppy disk drive 1     ",
			 diskname[1] );
	register_device( diskopen, diskclose, 0,
			 "RX0", "-- RX01 floppy disk drive 0     ",
			 diskname[0] );
}


/*************************************/
/* "officially visible" device state */
/*************************************/

static int command_register;
#define function  ((command_register >> 1) & 7)
#define unit      ((command_register >> 4) & 1)
#define eight_bit ((command_register >> 6) & 1)
#define maint     ((command_register >> 7) & 1)

static int interface_register;

static int error_code;
static int RXTA; /* track address */
static int RXSA; /* sector address */
static int RXES; /* error and status */

static unsigned char buffer[128]; /* one sector stored in RX01 controller */

static int transfer_request;
static int error_flag;
static int done_flag;
static int interrupt_enable;

/*************************/
/* Device implementation */
/*************************/

/* conditions to which the RX01 disk drive reacts */
#define rx01_idle 0
#define rx01_read 1
#define rx01_write 2
#define rx01_write_del 3
#define rx01_read_stat 4
/* function awaiting RX01 action */
static int rx01_function;

static void rx01_done()
/* called from within rx01_event() to indicate finishing of a rx01 operation */
{
	done_flag = 1;
	rx01_function = rx01_idle;
	if (interrupt_enable == 1) {
		irq = irq + 1;
	}
	interface_register = RXES;
}

static void rx01_event(p)
int p;
{ /* called from timer as each sector spins by disk heads */
	int u;

	switch (rx01_function) {
	case rx01_idle:
		break;
	case rx01_read:
		if (disk[unit] == NULL) { /* load head and find no media */
			error_code = 0110;
			error_flag = 1;
			rx01_done();
		} else if (current_sector[unit] == RXSA) {
			long int sectnum =
				  ((RXTA - min_track) * sectors_per_track)
				+ (RXSA - min_sector);
			fseek( disk[unit],
			       (sectnum * bytes_per_sector) + tag_length,
			       0 );
			RXES &= 07676; /* turn off DD and CRC bits */
			if(fread(buffer, bytes_per_sector, 1, disk[unit])!=1) {
				error_flag = 1;
				RXES |= 00001; /* report CRC error */
			} else {
				/* report deleted data bit */
				int byte =disktag[unit][tag_delete+(sectnum/8)];
				int bit = (byte >> (sectnum % 8)) & 1;
				RXES |= (bit << 6); /* report DD bit */
			}
			rx01_done();
		}
		break;
	case rx01_write:
	case rx01_write_del:
		if (disk[unit] == NULL) { /* load head and find no media */
			error_code = 0110;
			error_flag = 1;
			rx01_done();
		} else if (current_sector[unit] == RXSA) {
			long int sectnum =
				  ((RXTA - min_track) * sectors_per_track)
				+ (RXSA - min_sector);
			fseek( disk[unit],
			       (sectnum * bytes_per_sector) + tag_length,
			       0 );
			RXES &= 07676; /* turn off DD and CRC bits */
			if(fwrite(buffer, bytes_per_sector, 1, disk[unit])!=1){
				error_flag = 1;
				error_code = 0140; /* call it a bad header */
			} else {
				/* set bit in deleted data array */
				int bit = (rx01_function == rx01_write_del);
				disktag[unit][tag_delete+(sectnum/8)]
					&= ~(1 << (sectnum % 8));
				disktag[unit][tag_delete+(sectnum/8)]
					|= (bit << (sectnum % 8));
				RXES |= (bit << 6); /* report DD bit */
			}
			rx01_done();
		}
		break;
	case rx01_read_stat:
		if (disk[unit] == NULL) { /* load head and find no media */
			error_code = 0110;
			error_flag = 1;
			rx01_done();
		} else if (current_sector[unit] == min_sector) {
			RXES |= 0200; /* turn on Drv Rdy bit */
			RXES &= 0303; /* turn off Init Done bit */
			rx01_done();
		}
		break;
	}

	for (u = 0; u <= 1; u++) {
		if (disk[0] != NULL) { /* a disk is mounted */
			/* make it spin */
			current_sector[u] += 1;
			/* should allow other interleave factors */
			if (current_sector[u] > max_sector) {
				current_sector[u] -= sectors_per_track;
			}
		}
	}

	/* schedule next sector to spin by head */
	schedule( &rx01_delay, sector_time, rx01_event, 0 );
}

/* events to which the RX8E controller responds */
#define rx8_init      1 /* initialize */
#define rx8_init2     2 /* initialize */
#define rx8_init3     3 /* initialize */
#define rx8_init4     4 /* initialize */
#define rx8_init5     5 /* initialize */
#define rx8_init6     6 /* initialize */
#define rx8_xfer      7 /* ready for XDR */
#define rx8_fill8     8 /* 8 bit fill buffer from interface register */
#define rx8_fill12    9 /* 12 bit fill buffer from interface register */
#define rx8_empty8   10 /* 8 bit fill buffer into interface register */
#define rx8_empty12  11 /* 12 bit empty buffer into interface register */
#define rx8_seek     12 /* XDR involved with seek */
#define rx8_seekdone 13 /* second step in seek */
#define rx8_seekfail 14 /* third step in seek */
#define rx8_read_err 15 /* alternate third step in seek */
#define rx8_nop      16 /* no op is more complex than manual hints */

/* internal state of RX8E controller */
static int partial; /* partial completion state for multiple byte transfers */
static int bufpos;  /* position in buffer */

static void rx8_done()
/* called from withing rx8e_event() to indicate end of an rx8 operation */
{
	done_flag = 1;
	partial = 0;
	bufpos = 0;
	if (interrupt_enable == 1) {
		irq = irq + 1;
	}
	interface_register = RXES;
}

static void rx8e_event(p)
int p;
{ /* called from timer when an interface delay has completed */
	switch (p) {
	case rx8_init: /* initialize step 1, start seek + 10 on drive 1 */
		bufpos = 0;
		partial = 0;
		schedule( &rx8e_delay, track_time * 10,
			  rx8e_event, rx8_init2 );
		break;
	case rx8_init2: /* initialize step 2, start seek home drive 1 */
		current_track[1] += 10;
		if (current_track[1] > max_track) {
			current_track[1] = max_track;
		}
		schedule( &rx8e_delay, settle_time+track_time*current_track[1],
			  rx8e_event, rx8_init3 );
		break;
	case rx8_init3: /* initialize step 3, start seek + 10 on drive 0 */
		current_track[1] = min_track;
		schedule( &rx8e_delay, track_time * 10,
			  rx8e_event, rx8_init4 );
		break;
	case rx8_init4: /* initialize step 4, start seek home drive 0  */
		current_track[0] += 10;
		if (current_track[0] > max_track) {
			current_track[0] = max_track;
		}
		schedule( &rx8e_delay, settle_time+track_time*current_track[0],
			  rx8e_event, rx8_init5 );
		break;
	case rx8_init5: /* initialize step 5, start seek track 1 drive 0 */
		current_track[0] = min_track;
		schedule( &rx8e_delay, settle_time + track_time,
			  rx8e_event, rx8_init6 );
		break;
	case rx8_init6: /* initialize final step, fire up a read */
		current_track[0] = 1;
		rx01_function = rx01_read;
		break;
	case rx8_xfer: /* get ready for XDR */
		partial = 0;
		transfer_request = 1;
		break;
	case rx8_fill8: /* Fill Buffer, 8 bit mode */
		buffer[bufpos] = interface_register & 0377;
		bufpos += 1;
		if (bufpos >= bytes_per_sector) {
			rx8_done();
		} else {
			transfer_request = 1;
		}
		break;
	case rx8_fill12: /* Fill Buffer, 12 bit mode */
		/* note, in RX8E interface card, data is shifted into
		   the sector buffer serially, most significant bit
		   first.  Thus, byte 1 holds the top 8 bits of word
		   one, byte 2 the bottom 4 of word 1, then the top
		   4 of word 2, and byte 3 holds the bottom 8 of word 2 */
		if (partial == 0) {
			buffer[bufpos] = (interface_register&07760)>> 4;
			bufpos += 1;
			buffer[bufpos] = (interface_register&00017)<< 4;
			partial = 1;
		} else {
			buffer[bufpos] |=(interface_register&07400)>> 8;
			bufpos += 1;
			buffer[bufpos] = (interface_register&00377);
			bufpos += 1;
			partial = 0;
		}
		if (bufpos >= 96) {
			for (;bufpos < bytes_per_sector; bufpos++) {
				buffer[bufpos] = buffer[96];
			}
			rx8_done();
		} else {
			transfer_request = 1;
		}
		break;
	case rx8_empty8: /* Empty Buffer, 8 bit mode */
		if (bufpos < bytes_per_sector) {
			interface_register = buffer[bufpos];
			bufpos += 1;
			transfer_request = 1;
		} else {
			rx8_done();
		}
		break;
	case rx8_empty12: /* Empty Buffer, 12 bit mode */
		/* note, in RX8E interface card, data is shifted from
		   the buffer into the least significant bit of the
		   interface register.  Thus byte zero ends up in the
		   high 8 bits of the first word, followed by the top
		   4 bits of byte 1, in the low 4 bits of the word, and
		   the second word is made of the low 4 bits of byte 1
		   in the high 4 bits, followed by all of byte 2 in the
		   low 8 bits */
		if (bufpos < 96) {
			if (partial == 0) {
				interface_register =
				  (buffer[bufpos] << 4) |
				  (buffer[bufpos + 1] >> 4);
				bufpos += 1;
				partial = 1;
			} else {
				interface_register =
				  ((buffer[bufpos] & 017) << 8) |
				  buffer[bufpos+1];
				bufpos += 2;
				partial = 0;
			}
			transfer_request = 1;
		} else {
			rx8_done();
		}
		break;
	case rx8_seek: /* XDR after read or write command */
		if (partial == 0) { /* first, transfer RXSA */
		/*	RXSA = interface_register & 0377; */
			RXSA = interface_register & 0177; /* 7 bits */
			transfer_request = 1;
			partial = 1;
		} else if (partial == 1) { /* then, transfer RXTA, start IO */
			bufpos = 0;
			partial = 0;
			RXTA = interface_register & 0377; /* 8 bits */
			if ((RXTA > max_track)||(RXTA < min_track)) {
				/* bad track address */
				error_code = 0040;
				error_flag = 1;
				rx8_done();
			} else if ((RXSA < min_sector)||(RXSA > max_sector)) {
				/* bad sector */
				schedule( &rx8e_delay, 2*rotate_time,
					  rx8e_event, rx8_seekfail );
			} else { /* sector and track both valid */
				long int time;
				int tracks;
				tracks = RXTA - current_track[unit];
				if (tracks < 0) { tracks = -tracks; }
				time = settle_time + track_time * tracks;
				if (tracks == 0) { time = 0; }
				time += sector_time;
				schedule( &rx8e_delay, time,
					  rx8e_event, rx8_seekdone );
			}
		}
		break;
	case rx8_seekfail: /* seek failed, sector number invalid */
		error_code = 0070;
		error_flag = 1;
		rx8_done();
		break;
	case rx8_seekdone: /* seek worked */
		current_track[unit] = RXTA;
		partial = 0; /* !!! */
		switch (function) {
		case 02: /* Write Sector */
			rx01_function = rx01_write;
			break;
		case 03: /* Read Sector */
			rx01_function = rx01_read;
			break;
		case 06: /* Write Deleted Data Sector */
			rx01_function = rx01_write_del;
			break;
		}
		break;
	case rx8_read_err: /* read error register */
		rx8_done();
		interface_register = error_code;
		break;
	case rx8_nop: /* no-op (a bit complicated) */
		RXES &= 0303; /* turn off Init Done bit */
		rx8_done();
		break;
	}
}


/***********************************************/
/* Initialization used by CAF and reset switch */
/***********************************************/

rx8einit() /* console reset or programmed reset */
{
	int u;
	for (u = 0; u <= 1; u++) {
		if ( (current_track[u] <= min_track)
		   ||(current_track[u] >= max_track)) {
			current_track[u] = min_track;
		}
	}

	interface_register = 0;
	error_code = 0;
	RXES = 0;

	command_register = 00000; /* disk 0 */
	RXTA = 1;                 /* track 1 */
	RXSA = 1;                 /* sector 1 */

	transfer_request = 0;
	error_flag = 0;
	done_flag = 0;
	interrupt_enable = 0;
	/* assume the cpu clears irq */

	/* set disk motors spinning (if they're already spinning, no-op */
	rx01_function = rx01_idle;
	schedule( &rx01_delay, sector_time, rx01_event, 0 );

	/* load heads and wait for them to bounce */
	schedule( &rx8e_delay, settle_time, rx8e_event, rx8_init );
}

/********************/
/* IOT Instructions */
/********************/

rx8edev(op)
int op;
{
	switch (op) {
	case 00: /* NOP */
		break;
	case 01: /* LCD */
		command_register = ac;
		ac = 0;
		if (maint) {
			transfer_request = 1;
			error_flag = 1;
			done_flag = 1;
		} else switch (function) {
		case 00: /* Fill Buffer */
			schedule( &rx8e_delay, byte_time,
				  rx8e_event, rx8_xfer );
			break;
		case 01: /* Empty Buffer */
			if (eight_bit) {
				schedule( &rx8e_delay, byte_time,
					  rx8e_event, rx8_empty8 );
			} else {
				schedule( &rx8e_delay, word_time,
					  rx8e_event, rx8_empty12 );
			}
			break;
		case 02: /* Write Sector */
		case 03: /* Read Sector */
		case 06: /* Write Deleted Data Sector */
			schedule( &rx8e_delay, byte_time,
				  rx8e_event, rx8_xfer );
			break;
		case 04: /* No op */
			schedule( &rx8e_delay, byte_time,
				  rx8e_event, rx8_nop );
			break;
		case 05: /* Read Status */
			rx01_function = rx01_read_stat;
			break;
		case 07: /* Read Error Register */
			schedule( &rx8e_delay, byte_time,
				  rx8e_event, rx8_read_err );
			break;
		}
		break;
	case 02: /* XDR */
		if (maint) {
			ac |= interface_register;
		} else switch (function) {
		case 00: /* Fill Buffer */
			interface_register = ac;
			if (eight_bit) {
				schedule( &rx8e_delay, byte_time,
					  rx8e_event, rx8_fill8 );
			} else {
				schedule( &rx8e_delay, word_time,
					  rx8e_event, rx8_fill12 );
			}
			break;
		case 01: /* Empty Buffer */
			if (eight_bit) {
				ac |= interface_register;
				schedule( &rx8e_delay, byte_time,
					  rx8e_event, rx8_empty8 );
			} else {
				ac = interface_register;
				schedule( &rx8e_delay, word_time,
					  rx8e_event, rx8_empty12 );
			}
			break;
		case 02: /* Write Sector */
		case 03: /* Read Sector */
		case 06: /* Write Deleted Data Sector */
			interface_register = ac;
			schedule( &rx8e_delay, byte_time, rx8e_event,
				  rx8_seek );
			break;
		case 04: /* No op */
		case 05: /* Read Status */
			ac |= interface_register;
			break;
		case 07: /* Read Error Register */
			ac |= interface_register;
			break;
		}
		break;
	case 03: /* STR */
		if (transfer_request == 1) {
			pc = (pc + 1) & 07777;
		}
		if (!maint) {
			transfer_request = 0;
		}
		break;
	case 04: /* SER */
		if (error_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		if (!maint) {
			error_flag = 0;
		}
		break;
	case 05: /* SDN */
		if (done_flag == 1) {
			pc = (pc + 1) & 07777;
		}
		if (!maint) {
			if (interrupt_enable == 1) {
				irq = irq - done_flag;
			}
			done_flag = 0;
		}
		break;
	case 06: /* INTR */
		if (interrupt_enable == 1) {
			irq = irq - done_flag;
		}
		interrupt_enable = ac & 1;
		if (interrupt_enable == 1) {
			irq = irq + done_flag;
		}
		break;
	case 07: /* INIT */
		rx8einit();
		break;
	}
}
xxxxxxxxxx
cat > ttyaccess.c <<\xxxxxxxxxx
/* File: ttyaccess.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Modified by: Sander van Malssen, svm@kozmix.hacktic.nl.
	added fixes so it uses termios.h rather than sgtty.h
   Date:
   Language: C (UNIX)
   Purpose: Code to take over the tty and put it in raw mode
*/

/* WARNING:  This code is highly UNIX dependant!  Linux users may have to
   include a #define for TERMIOS to force use of the right include file.
   This define belongs in stdio.h, I think (a very small Linux bug).  */

#include <stdio.h>
#include <fcntl.h>

#ifdef TERMIOS
#include <termios.h>
#else
#include <sgtty.h>
#endif

#include <signal.h>

#define control(ch) (ch & 037)

#define keyboard 0
#define display 1


/*************/
/* operation */
/*************/

/* This code takes over you keyboard input and display output files so
   that the emulator will be able to do single keypress operations.

   As written here, it uses the UNIX ioctl() service to put the keyboard
   into raw-noecho mode (thus turning off all UNIX input processing,
   including break character detection and more), and then it uses
   fcntl() to allow polling of the input line.  It would be better
   to use UNIX signals to grab the keyboard interrupt, but an attempt at
   this caused too many headaches.

   Unfortunately, UNIX doesn't guarantee an interrupt for every key,
   so the interrupt service routine has to get one or more characters
   each time it is called; UNIX then adds insult to injury by providing
   lousy tools to handle the resulting critical sections.
*/


/************************/
/* startup and shutdown */
/************************/

#ifdef TERMIOS
static struct termios oldstate; /* tty state prior to reset */
#else
static struct sgttyb oldstate; /* tty state prior to reset */
#endif

static void mykill()  /* called to kill process */
{
	ttyrestore();
	exit(-1);
}

ttyraw() /* save tty state and convert to raw mode */
{
	/* take over the interactive terminal */
	{ /* get old TTY mode for restoration on exit */
#ifdef TERMIOS
		ioctl( keyboard, TCGETS, &oldstate );
#else
		ioctl( keyboard, TIOCGETP, &oldstate );
#endif
	}
	{ /* put TTY in RAW mode; note: raw mode may be a bit drastic! */
#ifdef TERMIOS
		struct termios newstate;
		ioctl( keyboard, TCGETS, &newstate );
		newstate.c_lflag &= ~ISIG;  /* don't enable signals */
		newstate.c_lflag &= ~ICANON;/* don't do canonical input */
		newstate.c_lflag &= ~ECHO;  /* don't echo */
		newstate.c_iflag &= ~INLCR; /* don't convert nl to cr */
		newstate.c_iflag &= ~IGNCR; /* don't ignore cr */
		newstate.c_iflag &= ~ICRNL; /* don't convert cr to nl */
		/* note:  on some UNIX systems, no amount of urging seems
		   to make it insist on converting cr to nl */
		ioctl( keyboard, TCSETS, &newstate );
#else
		struct sgttyb newstate;
		ioctl( keyboard, TIOCGETP, &newstate );
		newstate.sg_flags |= RAW;
		newstate.sg_flags &= ~ECHO;
		ioctl( keyboard, TIOCSETP, &newstate );
#endif
	}
	{ /* install handlers to catch attempts to kill process */
		/* should probably catch other fatal signals too */
		signal( SIGTERM, mykill );
	}
}

ttyrestore() /* return console to user */
{
#ifdef TERMIOS
	ioctl( keyboard, TCSETS, &oldstate );
#else
	ioctl( keyboard, TIOCSETP, &oldstate );
#endif
}


/*********************/
/* user I/O routines */
/*********************/

void (* ttybreak) () = NULL; /* set by user, called when 5 consec ^C seen */

ttyputc(ch) /* put character to console */
char ch;
{
	int count;
	char buf = ch;
	count = write( display, &buf, 1 );
}

#define BLOCKING 0
#define NONBLOCK 1
static int mode = BLOCKING; /* most recent tty read mode */

static int breakcount = 0; /* count of consecutive ^C chars while polling */

/* following definitions are for auxiliary path into TTY input stream */
#define stufflen 16
static int stuffhead = 0; /* head pointer for stuffing queue */
static int stufftail = 0; /* tail pointer for stuffing queue */
static char stuffqueue[stufflen];

void ttystuff(ch) /* stuff a char into input stream from auxiliary source */
char ch;
{
	int newtail = (stufftail + 1) % stufflen;
	if (newtail != stuffhead) { /* discards excess input */
		stuffqueue[stufftail] = ch;
		stufftail = newtail;
	}
}

int ttypoll() /* poll for a character from the console */
{
	int count;
	char buf;

	/* try to fill input buffer with one char */
	if (stuffhead != stufftail) {
		count = 1;
		buf = stuffqueue[stuffhead];
		stuffhead = (stuffhead + 1) % stufflen;
	} else {
		if (mode == BLOCKING) { /* make nonblocking */
			int flag;
			flag = fcntl( keyboard, F_GETFL, 0 );
#ifdef TERMIOS
			fcntl( keyboard, F_SETFL, flag | O_NDELAY );
#else
			fcntl( keyboard, F_SETFL, flag | FNDELAY );
#endif
			mode = NONBLOCK;
		}

		count = read( keyboard, &buf, 1 );
	}

	if (count <= 0) {
		return -1;
	} else {
		if (buf == control('c')) { /* detect 5 control C in a row */
			breakcount++;
			if ((breakcount >= 5) && (ttybreak != NULL)) {
				ttybreak();
				breakcount = 0;
			}
		} else {
			/* This should be dealt with in ttyraw(),
			   but many UNIX TTY interfaces refuse to do it!
			if (buf == '\n')
				buf = '\r'; */
			breakcount = 0;
		}
		return buf;
	}
}

int ttygetc() /* blocking 7 bit read from console */
{
	int count;
	char buf;

	if (stuffhead != stufftail) {
		count = 1;
		buf = stuffqueue[stuffhead];
		stuffhead = (stuffhead + 1) % stufflen;
	} else {
		if (mode != BLOCKING) { /* make nonblocking */
			int flag;
			flag = fcntl( keyboard, F_GETFL, 0 );
#ifdef TERMIOS
			fcntl( keyboard, F_SETFL, flag & ~O_NDELAY );
#else
			fcntl( keyboard, F_SETFL, flag & ~FNDELAY );
#endif
			mode = BLOCKING;
		}
		count = read( keyboard, &buf, 1 );
	}

	breakcount = 0;
	return buf & 0177;
}

ttyputs(buf) /* put string to console */
char * buf;
{
	int count;
	for (count = 0; buf[count] != '\0'; count++) {;};
	count = write( display, buf, count );
	fsync( display );
}

ttygets(buf,len) /* get string from console */
char * buf;
int len;
{
	int i = 0;
	int ch;
	do {
		ch = ttygetc();
		if (ch == '\b') {
			if (i > 0) {
				ttyputs( "\b \b" );
				i--;
			}
		} else if (ch >= ' ') {
			if (i < (len - 1)) {
				ttyputc(ch);
				buf[i] = ch;
				i++;
			}
		}
	} while ((ch != '\r') && (ch != '\n'));
	ttyputs( "\r\n" );
	if (i < len) {
		buf[i] = '\0';
	} else {
		buf[len - 1] = '\0';
	}
}
xxxxxxxxxx
cat > utility.c <<\xxxxxxxxxx
/* File: utility.c
   Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242.
   Date:
   Language: C (UNIX)
   Purpose: DEC PDP-8/e emulator utility routines
*/

#include <stdio.h>
#include "bus.h"


/* The following routine is used to make a copy of a string into a buffer *
 * that will be used as a file name.  This routine checks to make sure    *
 * that the file name fits in the buffer, and it prevents file names with *
 * embedded blanks by stopping the copy at the first blank.  It is safe   *
 * to call this with the same parameter for both arguments.               */

set_file_name(f,s)
char * f; /* buffer for file name */
char * s; /* string from which name comes */
{
        int i = 0; /* index into f */
        int j = 0; /* index into s */
        while ((i < (NAME_LENGTH - 1)) && (s[j] > ' ')) {
                f[i++] = s[j++];
        }
        f[i] = '\0';
}


/* command line argument processing */

getargs(argc, argv)
int argc;
char *argv[];
{
        int i;

	/* initialize argument flags etc to defaults */
	corename[0] = '\0';
	progname = argv[0];

	/* find real values of argument flags etc */
        for (i=1; i < argc; i++) {
		if (argv[i][0] == '-') {
			fprintf( stderr, "%s: unexpected option %s\n",
				 progname, argv[i] );
                        exit(-1);
                } else if (corename[0] != '\0') {
                        fprintf( stderr, "%s: too many core files specified\n",
                                 progname );
                        exit(-1);
                } else {
			set_file_name( corename, &argv[i][0] );
		}
        } /* end for loop */
}


/* The following two routines read and write core image files using a     *
 * simple variant of Charles Lasner's .IPL file format.                   *
 *                                                                        *
 * A core image file begins with an "auto-boot" header so that the host   *
 * operating system can run the file, loading the PDP-8 interpreter to    *
 * interpret its contents.  Under UNIX, this header is a line reading     *
 * "#!<i>", where "<i>" is replaced by the absolute file name of the      *
 * PDP-8 emulator.                                                        *
 *                                                                        *
 * In the remainder of the file, spaces and control characters are        *
 * ignored.  Immediately following the header, there may be any number    *
 * of lower-case ASCII characters, used as a comment.  These are ignored  *
 * in Lasner's standard, but here, if the comment begins with the string  *
 * "run", the machine is powered up in the running state.                 *
 * Following these are upper case characters (any ASCII character after   *
 * space and before the lower case characters), each representing one     *
 * 6 bit byte of the core image.  The two bytes of each 12 bit word are   *
 * recorded most significant bit first, and words are recorded starting   *
 * with word zero of memory.  The load data ends with the end of file,    *
 * a lower case letter, or the end of the emulator's memory, which ever   *
 * is encountered first.                                                  */

readcore()
{
	FILE *f;
	int  a; /* memory address to load */
	int  c,b; /* recent characters */
	int  d; /* counter used to pair up load bytes b and c */

	if ((f = fopen(corename, "r")) == NULL) {
		fprintf( stderr, "%s: cannot read core image from %s\n",
                         progname, corename );
		corename[0] = '\0';
	} else {
		if ((c = getc(f)) == '#') { /* skip emulator prefix */;
			do { /* skip until eol or eof */
				c = getc(f);
			} while ((c != EOF) && (c != '\n'));
		}

		c = getc(f);
		if (c == 'r') {
			c = getc(f);
			if (c == 'u') {
				c = getc(f);
				if (c == 'n') {
					run = 1;
				}
			}
		}
		while ((c != EOF) && (( c <= ' ') || (c >= 'a'))) {
			/* skip lower case ASCII leader */
			c = getc(f);
		}

		/* start of load data */
		d = 1;
		a = 0;
		for (;;) {
			b = c;
			do { /* get the next data character */
				c = getc(f);
			} while ((c != EOF) && (c <= ' '));
			if (c == EOF) break;
			if (c >= 'a') break;
			d++;
			if (d == 2) { /* we have a digraph */
				memory[a] = ((b - '!')<<6) | (c - '!');
				a++;
				d = 0;
			}
			if (a >= MAXMEM) break;
		}

		fclose(f);
	}
}

dumpcore()
{
	FILE *f;
	int a; /* memory address */
	int d; /* count of characters on this line */

	if ((f = fopen(corename, "w")) == NULL) {
		fprintf( stderr, "%s: cannot write core image to %s\n",
                         progname, corename );
		corename[0] = '\0';
	} else {
		/* first, output emulator prefix */
		fputs( "#!", f );
		fputs( PDP8NAME, f );
		putc( '\n', f );
		if (run == 1) {
			fputs( "run\n", f );
		}

		d = 0;
		for (a = 0; a < MAXMEM; a++) { /* dump words of memory */
			putc( (memory[a] >> 6) + '!', f );
			putc( (memory[a] & 077) + '!', f );
			d++;
			if (d >= 32) {
				putc( '\n', f );
				d = 0;
			}
		}
		fputs( "end\n", f );
		fclose(f);
	}
}
xxxxxxxxxx
