# shell archive created by dwjones on Tue Aug 26 09:30:04 AM CDT 2025 # To install this software on a UNIX compatable system (Linux or MacOS): # 1) create a directory (e.g. with the shell command mkdir project) # 2) change to that directory (e.g. with the command cd project), # 3) direct the remainder of this text to sh (e.g. sh < ../thisfile). # 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. # On other systems, extract files from this file using a text editor. # Each file is bracketed between two lines of xxxx. # The first line of xxxx contains a cat command giving the file name. cat > README <<\xxxxxxxxxx PDP-8/E emulator ================ Author: Douglas W. Jones Version: 8 Date: Aug 26, 2025 This is an emulator for the Digital Equipment Corporation PDP-8/E. It is written in C and tries to faithfully emulate the original computer, including instruction timings and input-output delays. It runs on Linux (and presumably, other Posix compliant systems). It has two options for the console interface, both of which use the shell window from which the emulator was launched as the console teletype. * One uses only the console shell for all operation. * One uses X11 windows to emulate the front panel lights and switches. Porting this code to non Posix (UNIX) systems will be difficult because of the use of low-level I/O and timers. Porting to window managers other than X11 will be difficult because of the use of the Xt toolkit. To run under Ubuntu Linux, you must install the X11 developer tools before trying to make the emulator: sudo apt-get -y install libx11-dev sudo apt-get -y install libxt-dev Other low-level tools may also be needed. Revision history ---------------- * Distribution 0, Nov. 29, 1993 * Distribution 1, Apr. 28, 1994 X-windows version, added PC8E support * Distribution 2, Jun. 30, 1994 upgrade X interface, add RX8E support * Distribution 3, Aug. 24, 1994 bug fixes * Distribution 4, Nov. 8, 1994 bug fixes * Distribution 5, Feb. 4, 1997 added CR8F and VC8E support * Distribution 6, Aug. 3, 2025 bring up to modern Linux standards * Distribution 7, Aug. 12, 2025 bug fixes, coremake supports BIN format * Distribution 8, Aug. 26, 2025 bug fixes in coremake when over 4K mem Files ----- Files corresponding to circuit boards or board clusters that would be plugged into the Omnibus backplane of a real computer are named using the option names for those components found in DEC documentation. Header files with the suffix .h follow C programming conventions for describing the public interfaces to system components. This distribution contains the following files: * README -- this file * Makefile -- instructions for configuring, compiling and linking * bus.h -- logically, the emulated omnibus (global variables) * kk8e.h * kk8e.c -- the CPU * kc8.h -- the public interface to any of the console options * kc8e.h -- specific additiona interfaces to the X-windows console * kc8e.c -- the X (graphic blinking lights) console * kc8m.c -- the teletype-based console * km8e.h * km8e.c -- the extended memory option (MMU, to use modern terminology) * dk8e.h * dk8e.c -- the real-time clock option * debug.h * debug.c -- a tool to trace memory and instruction usage * kl8e.h * kl8e.c -- the console terminal interface * pc8e.h * pc8e.c -- an emulated paper-tape reader-punch * cr8f.h * cr8f.c -- an emulated punched-card reader * rx8e.h * rx8e.c -- an emulated dual 8-inch floppy-disk drive * vc8e.c * vc8e.h -- an emulated oscilloscope display * utility.c * utility.h -- emulator support utilities * devices.c * devices.h -- register the mounting of files on devices * ttyaccess.c * ttyaccess.h -- access to the console terminal * realtime.c * realtime.h -- core simulation routines to advance time * hobble.h * hobble.c -- mechanism to keep simulated time in step with real time * coredump.c -- auxiliary tool to print a saved core image in readable octal * coremake.c -- auxiliary tool to build a core image file from assembler output Installation ------------ Once you unpack the shell archive on a UNIX system, run `make`. This will follow the instructions in `Makefile` to build an executable called `pdp8e`. You should install this executable somewhere on your search path. 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. Read the comments in `Makefile` for more detail. Any options you would have to change by soldering jumpers on a device interface board on the real machine are selected in this emulator by editing the source code. For example, the emulated baud rate of the console terminal can be changed this way. By default, it is 110 baud (10 characters per second, the rate for a Teletype model 33). You can also `make coremake` and `make coredump` to make two utility programs. * `coremake` builds a core image file from RIM or BIN assembler output. * `coredump` dumps a core image file in somewhat readable octal. Use --- To run the emulator, after making it, type `pdp8e` (the default name for the emulator), or `./pdp8e` if you have not installed the executable on your search path. If you type `pdp8e -f` where `f` is the name of a file, the emulator will initialize its core memory from that file and (on exit) save the memory state in that file. If a core image file is marked as executable, executing that file will launch the emulator with its memory initialized from that file. This emulates the fact that core memory is non-volitile, so you can power down a PDP-8 and then power it up later and whatever is in memory at shutdown will still be there on power up. If you have a copy of the PAL assembler, and an assembler source file to run a demo, the following sequence of commands will assemble the source, build a core image, and run the emulator with that core image pre-loaded: > `pal demo.pal` > `coremake demo < demo.bin` > `pdp8e demo` If you do not make your core image file read-only, normal exit from the emulator will overwrite it with the contents of core at shutdown. As a result, core image files simulate the non-volatile behavior of the core memory on a real PDP-8. **The KK8M console** The KK8M console interface to this emulator is teletype based, with a command language based on ODT. When the emulator starts, it comes up halted. At this point, or whenever the PDP-8 halts, it will print > `ppppp (aaaa` where `ppppp` is the program counter in octal and `aaaa` is the contents of the accumulator, also in octal. There is a help command `?`. Briefly, `nnnnnG` sets the program counter to `nnnnn` and starts the computer and `C` continues from halt. When running, you can return to the console debugger by typing 5 consecutive control C characters. Single control C characters go to the running application on the PDP-8. Other commands let you edit memory and registers, mount and dismount files to emulate storage media, and access the optional debug history tool. **The KK8E console** The KK8E console interface opens a window on the screen holding an imitation with artwork imitating a PDP-8/E front panel. A second window will appear if the VC8E scope display option is installed. 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. The function keys F1 through F12, if present on your keyboard, will toggle the switches in the switch register, possibly speeding some front panel operations. 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. Card deck files begin with the ASCII prefix H80 (H for Hollerith coding, 80 for 80 column cards) followed by a sequence of 123-byte records, one per punched card. The first 3 bytes of the record are instructions for displaying the card on a card-deck display tool, while the remaining 120 bytes hold 80 12-bit codes. The details are documented here: https://www.cs.uiowa.edu/~dwjones/cards/format.html Links to tools for creating and manipulating card decks are here: https://www.cs.uiowa.edu/~dwjones/cards/ Bugs ---- * The fonts used by the KC8E were selected long ago, and the fonts used have mostly disappeared from the X11 default font collecton. Someone needs to browse the X11 font world to find better fonts. * Exiting the KC8E console via the window manager leaves the terminal window in no-echo mode. Use the power-off control on the lower left of the KC8E console or the exit button next to the help button to avoid this. Someone needs to find out how the window manager tells the emulator to exit. License ------- This is freeware, of uncertain function and uncertain utility. There are no restrictions on reuse or further distribution of this code for any purpose whatsoever. Warranty -------- You get what you pay for, use this code at your own risk. If this code works for you, that's great. If it doesn't, you are out of luck. If you report bugs to the author, the author might fix them. xxxxxxxxxx cat > Makefile <<\xxxxxxxxxx # make a PDP-8 emulator # Author: Douglas W. Jones # Date: Aug. 12, 2025 # # 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 intermediate files left behind by above # 6, make pdp8e.shar to create a shell archive of the whole project. ########################################################################## # # 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 factors: # # IOFUDGE = 1 makes the emulator run with simulated I/O timings # indistinguishable from real hardware, as measured by software on the # PDP-8 by 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 of IOFUDGE: # for old slow computers: For IBM RT, 20; 25Mhz SPARC, 5 # for post 2000 computers, 1 because these machines are naturally fast # # HOBBLE, if defined, slows down the simulator to try to make simulated # time match real time, so that simulated PDP-8 computations take the # same time on the simulator as they would in real life. The value of # HOBBLE sets the number of milliseconds between corrections. # # Recommended values of HOBBLE: # for old slow computers, do not define HOBBLE # for simulators with just TTY interfaces, 50 (20 corrections/sec) # for simulators with front panel animation, 33 (30 corrections/sec) FUDGE = -DIOFUDGE=1 -DHOBBLE=33 fudge = hobble.o #FUDGE = -DIOFUDGE=20 #fudge = ########################################################################## # # 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) # KC8Esize in [0,1] determines size of console (small or large) console = kc8e.o CONSOLE = -DKC8E -DKC8Esize=0 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 # without this option, the machine may only address 4K of memory. # with this option, the machine may address 32K of memory. # DK8E -- Real-Time Clock, M882 or M8830 # the interrupt rate is selectable by editing dk8e.c #intern = #INTERN = intern = km8e.o INTERN = -DKM8E #intern = km8e.o dk8e.o #INTERN = -DKM8E -DDK8E #---- 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 # VC8E -- Vector (actually point-plot) CRT display, M869 + M885 # the display type and size is selectable by editing vc8e.c # this requires the KC8E (x-windows) console # CR8F -- CR8F Card reader and Control # RX8E -- RX01 diskette drive, M8357 interface #extern = kl8e.o pc8e.o #EXTERN = -DKL8E -DPC8E #extern = kl8e.o pc8e.o cr8f.o rx8e.o #EXTERN = -DKL8E -DPC8E -DCR8F -DRX8E extern = kl8e.o pc8e.o cr8f.o rx8e.o vc8e.o EXTERN = -DKL8E -DPC8E -DCR8F -DRX8E -DVC8E #---- Memory; on a real machine, the amount of memory can be selected # as any multiple of 4096 up to 32768. Here, to avoid the cost of # address validity checking, partial memory configurations may not # be specified. The constant MAXMEM is therefore autoset in bus.h # to 4096 unless the KM8E option is selected, whereupon it is 32768. ########################################################################## # # End of machine configuration # # What follows will only need editing if the source code is changed. ########################################################################## # # Patch together the list of object files and the list of compiler # options from the above INTERP = -DPDP8NAME=\"$(INTERPRETER)\" OPTIONS = $(CPU) $(CONSOLE) $(INTERN) $(EXTERN) $(FUDGE) $(INTERP) objects = $(cpu) $(console) $(intern) $(extern) $(fudge) libraries = $(conslib) utility = realtime.o ttyaccess.o utility.o devices.o ########################################################################## # # primary make target, the PDP-8 emulator $(INTERPRETER): $(objects) $(utility) cc -o $(INTERPRETER) $(objects) $(utility) $(libraries) ########################################################################## # # makefile dependencies of emulator utility components realtime.o: realtime.c realtime.h cc -c realtime.c hobble.o: hobble.c hobble.h realtime.h Makefile cc -c hobble.c $(FUDGE) ttyaccess.o: ttyaccess.c ttyaccess.h cc -c ttyaccess.c utility.o: utility.c utility.h bus.h kc8.h utility.o: Makefile cc -c utility.c $(INTERP) $(INTERN) devices.o: devices.c devices.h cc -c devices.c debug.o: debug.c bus.h ttyaccess.h cc -c debug.c $(INTERN) ########################################################################## # # Makefile dependencies for the primary emulator components # cpu kk8e.o: kk8e.c kk8e.h utility.h debug.h devices.h ttyaccess.h kk8e.o: kc8.h km8e.h dk8e.h kl8e.h pc8e.h cr8f.h vc8e.h rx8e.h kk8e.o: Makefile cc -c kk8e.c $(OPTIONS) # x windows console kc8e.o: kc8e.c kc8.h kc8e.h realtime.h bus.h ttyaccess.h debug.h devices.h kc8e.o: kk8e.h kc8e.o: Makefile cc -c kc8e.c $(CONSOLE) $(INTERN) $(CPU) # tty console kc8m.o: kc8m.c kc8.h realtime.h bus.h ttyaccess.h debug.h devices.h kc8m.o: kk8e.h kc8m.o: Makefile cc -c kc8m.c $(CONSOLE) $(INTERN) $(CPU) # extended memory km8e.o: km8e.c km8e.h bus.h km8e.o: Makefile cc -c km8e.c $(INTERN) # real time clock dk8e.o: dk8e.c dk8e.h realtime.h bus.h dk8e.o: Makefile cc -c dk8e.c $(FUDGE) $(INTERN) # console terminal interface kl8e.o: kl8e.c kl8e.h realtime.h bus.h ttyaccess.h kl8e.o: Makefile cc -c kl8e.c $(FUDGE) $(INTERN) # paper tape reader-punch pc8e.o: pc8e.c pc8e.h realtime.h bus.h utility.h devices.h pc8e.o: kc8.h pc8e.o: Makefile cc -c pc8e.c $(FUDGE) $(INTERN) # card reader cr8f.o: cr8f.c cr8f.h realtime.h bus.h utility.h devices.h cr8f.o: kc8.h cr8f.o: Makefile cc -c cr8f.c $(FUDGE) $(INTERN) # scope display vc8e.o: vc8e.c vc8e.h realtime.h bus.h vc8e.o: kc8e.h cc -c vc8e.c $(INTERN) # floppy disk drive rx8e.o: rx8e.c rx8e.h realtime.h bus.h utility.h devices.h rx8e.o: kc8.h rx8e.o: Makefile cc -c rx8e.c $(FUDGE) $(INTERN) ########################################################################## # # Secondary utilities # this makes a utility to print an octal dump of an emulator core image coredump.o: coredump.c bus.h utility.h devices.h coredump.o: Makefile cc -c coredump.c $(INTERN) coredump: coredump.o utility.o devices.o cc -o coredump coredump.o utility.o devices.o # this makes a utility to load from RIM paper tape image into emulated core coremake.o: coremake.c bus.h utility.h coremake.o: Makefile cc -c coremake.c $(INTERN) coremake: coremake.o utility.o devices.o cc -o coremake coremake.o utility.o devices.o # make clean to delete the object files, saving disk space clean: rm -f *.o # make shar to make a Unix shell archive of all source files MKEYS = bus.h kk8e.h kk8e.c kc8.h kc8e.h kc8e.c kc8m.c MINTS = km8e.h km8e.c dk8e.h dk8e.c debug.h debug.c MEXTS = kl8e.h kl8e.c pc8e.h pc8e.c cr8f.h cr8f.c rx8e.h rx8e.c vc8e.c vc8e.h MSUPP = utility.c utility.h ttyaccess.c ttyaccess.h realtime.c realtime.h devices.h devices.c hobble.h hobble.c MTOOL = coredump.c coremake.c MSOURCES = README Makefile $(MKEYS) $(MINTS) $(MEXTS) $(MSUPP) $(MTOOL) pdp8e.shar: $(MSOURCES) shar $(MSOURCES) > pdp8e.shar xxxxxxxxxx cat > bus.h <<\xxxxxxxxxx /* File: bus.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 26, 1995 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" -- now provided by Makefile */ /* 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 not done, so all addressable memory must be available. Thus, the memory size depends on whether the KM8E option is present. */ #ifdef KM8E #define MAXMEM 32768 #else #define MAXMEM 4096 #endif 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 linkbit;/* 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 int km8e_uif; /* user interrupt flag (local to KM8E but used in KK8E) */ #endif xxxxxxxxxx cat > kk8e.h <<\xxxxxxxxxx /* File: kk8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC PDP-8/e emulator */ /* Both the reset key on the console and the CAF instruction call this */ void clearflags(); /* called only once for power on */ void powerup( int argc, char *argv[] ); /* called only once from the console to exit the emulator */ void powerdown(); xxxxxxxxxx cat > kk8e.c <<\xxxxxxxxxx /* File: kk8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug 4, 2025 Language: C (UNIX) Purpose: DEC PDP-8/e emulator Based on the PDP-8/E Small Computer Handbook, Digital Equipment Corporation, 1971, 1973, and the PDP-8/E/F/M Maintenance Manual, volumes 1 and 2, Digital Equipment Corporation, 1975. */ /* First, declare that this is a main program */ #define MAIN #include #include #include #include "bus.h" #include "realtime.h" #include "ttyaccess.h" #include "utility.h" #include "devices.h" #include "kc8.h" #ifdef DEBUG #include "debug.h" #endif #ifdef HOBBLE #include "hobble.h" #endif #ifdef KM8E #include "km8e.h" #endif #ifdef DK8E #include "dk8e.h" #endif #ifdef KL8E #include "kl8e.h" #endif #ifdef PC8E #include "pc8e.h" #endif #ifdef CR8F #include "cr8f.h" #endif #ifdef VC8E #include "vc8e.h" #endif #ifdef RX8E #include "rx8e.h" #endif #include "kk8e.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 */ void 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 KL8E kl8einit(); /* console TTY */ #endif #ifdef PC8E pc8einit(); /* paper tape reader punch */ #endif #ifdef CR8F cr8finit(); /* card reader */ #endif #ifdef VC8E vc8einit(); /* point plot display */ #endif #ifdef RX8E rx8einit(); /* diskette drive */ #endif linkbit = 000000; ac = 00000; irq = 0; enab = 0; enab_del = 0; } static void closecore( int u ) { corename[0] = '\0'; } static bool opencore( int u, char *f ) { set_file_name( corename, f ); return true; } /* called only once for power on */ void powerup( 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 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 ); /* take over the TTY */ ttybreak = NULL; ttyraw(); /* initialize the real-time simulation mechanisms */ init_timers(); /* initialize all devices to their power-on state */ /* core must be registered first because it's going to be on the console device window at console-power up */ register_device( opencore, closecore, 0, "CORE", "- saved image of main memory ", corename ); kc8power( argc, argv ); /* console */ #ifdef HOBBLE hobblepower(); #endif #ifdef KE8E ke8epower(); /* eae */ #endif #ifdef KE8E km8epower(); /* mmu */ #endif #ifdef DK8E dk8epower(); /* real-time clock */ #endif #ifdef KL8E kl8epower(); /* console TTY */ #endif #ifdef PC8E pc8epower(); /* paper tape reader punch */ #endif #ifdef CR8F cr8fpower(); /* card reader */ #endif #ifdef VC8E vc8epower(); /* point plot display */ #endif #ifdef RX8E rx8epower(); /* diskette drive */ #endif /* now, with all devices set up, mount devices, as needed */ if (corename[0] != '\0') { /* there is a core file */ readcore(); /* if the machine was running when last stopped, this may set the run flipflop, it might also mount many devices */ } clearflags(); } /* called only once from the console to exit the emulator */ void powerdown() { ttyrestore(); if (corename[0] != '\0') { /* there is a core file */ dumpcore( corename ); } close_devices(); exit( EXIT_SUCCESS ); } /************************/ /* 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 */ int main( 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 | linkbit) + memory[cpma]; linkbit = ac & 010000; ac = ac & 007777; countdown -= longcycle; break; case opTAD | DIRECT | CURRENT: CURRENT_PAGE; pc = (pc + 1) & 07777; ac = (ac | linkbit) + memory[cpma]; linkbit = ac & 010000; ac = ac & 007777; countdown -= longcycle; break; case opTAD | DEFER | ZERO: PAGE_ZERO; pc = (pc + 1) & 07777; DEFER_CYCLE; ac = (ac | linkbit) + memory[cpma]; linkbit = ac & 010000; ac = ac & 007777; countdown -= longcycle; break; case opTAD | DEFER | CURRENT: CURRENT_PAGE; pc = (pc + 1) & 07777; DEFER_CYCLE; ac = (ac | linkbit) + memory[cpma]; linkbit = 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 = (linkbit >> 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 */ linkbit = (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 VC8E case 005: vc8edev5(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 CR8F case 063: cr8fdev3(mb & 07); break; case 067: cr8fdev7(mb & 07); 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 */ linkbit = linkbit ^ 010000; break; case 002: /* CMA */ ac = ac ^ 007777; break; case 003: /* CMA CML */ ac = ac ^ 007777; linkbit = linkbit ^ 010000; break; case 004: /* CLL */ linkbit = 000000; break; case 005: /* CLL CML */ linkbit = 010000; break; case 006: /* CLL CMA */ ac = ac ^ 007777; linkbit = 000000; break; case 007: /* CLL CMA CML */ ac = ac ^ 007777; linkbit = 010000; break; case 010: /* CLA */ ac = 00000; break; case 011: /* CLA CML */ ac = 00000; linkbit = linkbit ^ 010000; break; case 012: /* CLA CMA */ ac = 07777; break; case 013: /* CLA CMA CML */ ac = 07777; linkbit = linkbit ^ 010000; break; case 014: /* CLA CLL */ ac = 00000; linkbit = 000000; break; case 015: /* CLA CLL CML */ ac = 00000; linkbit = 010000; break; case 016: /* CLA CLL CMA */ ac = 07777; linkbit = 000000; break; case 017: /* CLA CLL CMA CML */ ac = 07777; linkbit = 010000; break; } if (mb & 00001) { /* IAC */ ac = (ac | linkbit) + 1; linkbit = 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) | (linkbit >> 12); linkbit = ac & 010000; ac = ac & 007777; break; case 03: /* RAL TWO */ ac = (ac << 2) | ((ac | linkbit) >> 11); linkbit = ac & 010000; ac = ac & 007777; break; case 04: /* RAR */ ac = ((ac | linkbit) >> 1) | (ac << 12); linkbit = ac & 010000; ac = ac & 007777; break; case 05: /* RAR TWO */ ac = ((ac | linkbit) >> 2) | (ac << 11); linkbit = 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 (linkbit) { pc = (pc + 1) & 07777; } break; case 003: /* SNL REV */ if (linkbit == 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) || linkbit) { pc = (pc + 1) & 07777; } break; case 007: /* SZA SNL REV */ if (ac && (linkbit == 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 | linkbit) & 014000) { pc = (pc + 1) & 07777; } break; case 013: /* SMA SNL REV */ if (((ac | linkbit) & 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 | linkbit) & 014000) || (ac == 0)) { pc = (pc + 1) & 07777; } break; case 017: /* SMA SZA SNL REV */ if ((((ac | linkbit) & 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 (EAEmode == 0) { /* mode A */ if (mb & 00040) { /* mode A SCA */ ac |= sc; } switch ((mb & 00016) >> 1) { case 00: /* NOP */ break; case 01: /* SCL */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; sc = (~mb) & 00037; break; case 02: /* MUY */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; { long int prod = mp * mb; mq = prod & 07777; ac = (prod>>12) & 07777; linkbit = 000000; } sc = 013; break; case 03: /* DVI */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; if (ac < mb) { /* no overflow */ long int idend; idend = (ac << 12) | mq; mq = idend / mb; ac = idend % mb; linkbit = 000000; } else { /* overflow */ /* --- mq = ?? --- */ ac = ac - mb; /* shift ac-mq-link linkbit = 010000; /* --- shift ?? --- */ } sc = 014; break; case 04: /* NMI or SWAB */ if ((mb & 00060) == 020) { /* SWAB */ EAEmode = 1 } else { /* NMI */ long int shift, news; shift = (link | ac)<<12; shift |= mq; sc = 0; do (;;) { news = shift << 1; if(!(news & 027777777)) break; if ( (news^shift) & 040000000 ) break; shift = news; sc ++; } mq = shift & 07777; shift >>= 12; ac = shift & 07777; linkbit = shift & 010000; } break; case 05: /* SHL */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; sc = (~mb) & 00037; } long int shift; shift = (link | ac)<<12; shift |= mq; do (;;) { shift <<= 1; sc ++;sc &= 037; if (sc == 0) break; } mq = shift & 07777; shift >>= 12; ac = shift & 07777; linkbit = shift & 010000; } break; case 06: /* ASR */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; sc = (~mb) & 00037; } long int shift; shift = (ac<<12)|mq; linkbit = (ac<<1)&010000; do (;;) { shift=(link|shift)>>1; sc ++;sc &= 037; if (sc == 0) break; } mq = shift & 07777; shift >>= 12; ac = shift & 07777; } break; case 07: /* LSR */ #ifdef KM8E cpma = pc | ifr; #else cpma = pc; #endif mb = memory[cpma]; pc = (pc + 1) & 07777; sc = (~mb) & 00037; } long int shift; shift = (ac<<12)|mq; do (;;) { shift >>= 1; sc ++;sc &= 037; if (sc == 0) break; } mq = shift & 07777; shift >>= 12; ac = shift & 07777; linkbit = 0; } break; } } else { /* mode B */ if ((mb & 00040) == 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 > kc8.h <<\xxxxxxxxxx /* File: kc8.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 4, 2025 Language: C (UNIX) Purpose: DEC front panel interface for kc8e and kc8m. */ /**********************************************************/ /* Interface between cpu implementation and control panel */ /**********************************************************/ /* power-on initialize */ void kc8power( int argc, char** argv ); /* console reset */ void kc8init(); /* respond to halt instruction */ void kc8halt(); xxxxxxxxxx cat > kc8e.h <<\xxxxxxxxxx /* File: kc8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 12, 2025 Language: C (UNIX) Purpose: interface to DEC KC8/E control panel emulator */ /* CONTEXT: include kc8.h and X11 includes before this */ /*******************************************/ /* interface specific to window management */ /*******************************************/ /* needed to allow PDP-8 devices to access window manager */ void kc8getinfo( Display **d, int *s ); /* needed to allow PDP-8 devices to make their own device windows */ Widget kc8makepopupshell(); /* needed to allow keyboard to remain active in PDP-8 device windows */ void handle_key_press( Widget w, XtPointer d, XEvent *e, Boolean *cont ); xxxxxxxxxx cat > kc8e.c <<\xxxxxxxxxx /* File: kc8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 12, 2025 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 #include #include #include #include #include #include #include #include #include #include #define XK_MISCELLANY #include #include "realtime.h" #include "bus.h" #include "devices.h" #include "ttyaccess.h" #include "kk8e.h" #ifdef DEBUG #include "debug.h" #endif #include "kc8.h" #include "kc8e.h" /***************************/ /* User controlled options */ /***************************/ /* default frequency for console display update and X-events polling */ #define default_console_interval (10 * millisecond + 1) /* characteristics of heartbeat display on control panel */ #define heartbeat_interval (1L * second) #define on_interval (100L * millisecond) /* KC8Eheight is the height of the console, in pixels, it must be divisible by 30; console width is 2.1 times height KC8Eheight = 300 is the original and minimum value. KC8Esize = 0 gives this, 1 gives 450; defined in the Makefile */ #define KC8Eheight ((KC8Esize + 2) * 150) /**************************/ /* Font selection */ /**************************/ #if (KC8Esize == 0) #define HEADfont "-*-helvetica-bold-r-*-*-18-180-*-*-*-*-ISO8859-1" #define HEADfonta "-*-arial-bold-r-*-*-18-180-*-*-*-*-ISO8859-1" #define HEADfontb "-*-*-*-r-*-*-16-*-*-*-*-*-ISO8859-1" #define SUBfont "-*-helvetica-bold-r-*-*-10-100-*-*-*-*-ISO8859-1" #define SUBfonta "-*-arial-bold-r-*-*-10-100-*-*-*-*-ISO8859-1" #define SUBfontb "-*-*-*-r-*-*-10-*-*-*-*-*-ISO8859-1" #define TINYfont "-*-clean-medium-r-*-*-6-*-*-*-*-*-ISO8859-1" #define TINYfonta "-*-veranda-medium-r-*-*-6-*-*-*-*-*-ISO8859-1" #define TINYfontb "-*-*-*-r-*-*-6-*-*-*-*-*-ISO8859-1" #define HELPfont "-*-helvetica-medium-r-*-*-12-*-*-*-*-*-ISO8859-1" #define HELPfonta "-*-arial-medium-r-*-*-12-*-*-*-*-*-ISO8859-1" #define HELPfontb "-*-*-*-r-*-*-12-*-*-*-*-*-ISO8859-1" #elif (KC8Esize == 1) #define HEADfont "-*-helvetica-bold-r-*-*-27-180-*-*-*-*-ISO8859-1" #define HEADfonta "-*-arial-bold-r-*-*-27-180-*-*-*-*-ISO8859-1" #define HEADfontb "-*-*-*-r-*-*-24-*-*-*-*-*-ISO8859-1" #define SUBfont "-*-helvetica-bold-r-*-*-15-100-*-*-*-*-ISO8859-1" #define SUBfonta "-*-arial-bold-r-*-*-15-100-*-*-*-*-ISO8859-1" #define SUBfontb "-*-*-*-r-*-*-20-*-*-*-*-*-ISO8859-1" #define TINYfont "-*-veranda-medium-r-*-*-10-*-*-*-*-*-ISO8859-1" #define TINYfonta "-*-clean-medium-r-*-*-10-*-*-*-*-*-ISO8859-1" #define TINYfontb "-*-*-*-r-*-*-9-*-*-*-*-*-ISO8859-1" #define HELPfont "-*-helvetica-medium-r-*-*-18-*-*-*-*-*-ISO8859-1" #define HELPfonta "-*-arial-medium-r-*-*-18-*-*-*-*-*-ISO8859-1" #define HELPfontb "-*-*-*-r-*-*-18-*-*-*-*-*-ISO8859-1" #else ILLEGAL_KC8Esize_value #endif /**************************/ /* Window data structures */ /**************************/ XtAppContext app_context; /* the topmost context for everything */ static Widget appshell; /* the topmost widget */ static Widget frontpanelshell; /* a framework for the front panel */ static Widget frontpanel; /* the console front panel */ /*******************************/ /* 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) */ /* 16 by 16 bitmap data for dark shades */ static char dark_bits[] = { 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 void boxshow( 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; /* make a help message appear, with OK button armed to make it go away */ static void showhelpmsg( char *m ) { helpbox->label = m; helpbox->lablen = strlen(helpbox->label); helpbox->rule = linefillbox; helpbox->sub = helpok; boxshow( helpbox ); } static void boxpress( int x, int 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 ); } } /* 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 */ static struct box_rec* makebox( struct box_rec *b ) { 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 void changeregs(); /* forward declartion */ /* change state of switch with top-level box b */ static int toggleswitch( struct box_rec *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; /* called internally to make a momentary switch change back */ static void bounceinternal() { (void) toggleswitch( toggle ); toggle = NOBOX; } /* called to mark a button-press on a momentary switch */ static void bounceswitch( struct box_rec *b ) { (void) toggleswitch(b); changeregs(); toggle = b; } /* called to make a switch from prototype b with initial state 0 or 1 */ static struct box_rec* makeswitch( struct box_rec *b, int state ) { 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 and some of the switches */ static struct box_rec* cpmaboxes[15]; /* bits of cpma */ static struct box_rec* runbox; /* run */ static struct box_rec* databoxes[12]; /* data display */ static struct box_rec* srboxes[12]; /* switch register */ /* 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 (linkbit >> 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 void 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] ); } } } } /******************************************/ /* button press event handling procedures */ /******************************************/ /* hook to call for keypress (char param) */ static void (* keypress)(char) = NULL; /* mouseclick in background, do nothing */ static void backpress( struct box_rec *b ) { } /* box records globally known to logopress so special panel can be toggled */ static struct box_rec* logobox; /* the box holding logo or control panel */ static struct box_rec* decbox; /* the box holding the DEC logo */ static struct box_rec* panelbox; /* the box holding panel */ /* heartbeat */ static struct box_rec* heartbeat_box; /* heartbeat on the alternate panel*/ static struct timer heartbeat_delay; static void heartbeat() { if (heartbeat_box->boxGC == blackGC) { heartbeat_box->boxGC = greyGC; schedule( &heartbeat_delay, on_interval, heartbeat, NOPARAM ); } else { heartbeat_box->boxGC = blackGC; schedule( &heartbeat_delay, heartbeat_interval - on_interval, heartbeat, NOPARAM ); } if (logobox->sub == panelbox) { boxshow( heartbeat_box ); } } static void helppress( struct box_rec *b ) { showhelpmsg( b->help ); } /* mouseclick in exit button or power control area, shutdown */ static void powerpress( struct box_rec *b ) { /* note, b is always ignored, NULL is legal here */ /* ... first clean up X? ... */ XtDestroyApplicationContext( app_context ); powerdown(); } /* window closed through window manager */ static void powercallback( Widget w, XtPointer clint_dat, XtPointer call_dat ) { powerpress( NULL ); } #ifdef DEBUG /* mouse click in button requesting diagnostic trace information */ static void tracepress( struct box_rec *b ) { 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 */ /* mouse click on minus box to decrease console interval */ static void minuspress( struct box_rec *b ) { 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 ); } /* mouse click on minus box to decrease console interval */ static void pluspress( struct box_rec *b ) { 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 */ static struct box_rec* device_name_box; /* display box for device name */ static struct box_rec* file_name_box; /* display box for file name */ static int filenameactive = 0; /* state of file name acquisition routines */ /* mouse click down box to scroll through list of devices */ static void dnpress( struct box_rec *b ) { if (filenameactive == 0) { /* only allowed if not in mid name change */ next_device(); device_name_box->label = device_name(); device_name_box->lablen = strlen( device_name_box->label ); device_name_box->help = device_longname(); file_name_box->label = device_file(); file_name_box->lablen = strlen( file_name_box->label ); boxshow( device_name_box ); boxshow( file_name_box ); } else { showhelpmsg( "please finish typing a new file name" ); } } /* keypress in file name box */ static void filekeypress( char c ) { 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 (!device_mount()) { /* try mount current device */ showhelpmsg( "this file cannot be mounted on that device"); } file_name_box->lablen = strlen( file_name_box->label ); } 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 ); } } } /* mouseclick in the box to mount a file on a device */ static void filepress( struct box_rec *b ) { if (filenameactive == 0) { /* if not gathering file name, set state to gathering name */ filenameactive = 1; /* first dismount previous file from current device */ device_dismount(); /* make sure result of dismount is displayed */ file_name_box->label = device_file(); file_name_box->lablen = strlen( file_name_box->label ); /* 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 */ filekeypress( '\r' ); } } /* toggle between DEC logo and special control panel */ static void logopress( struct box_rec *b ) { if (filenameactive == 0) { /* do it only if file name is entered */ if (logobox->sub == decbox) { logobox->sub = panelbox; } else { logobox->sub = decbox; } boxshow( logobox ); } else { showhelpmsg( "please finish typing a new file name"); } } /* mouseclick in data display selection area */ static void datapress( struct box_rec *b ) { /* 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(); } /* mouseclick on sw switch */ static void swpress( struct box_rec *b ) { sw = toggleswitch(b); changeregs(); } /* mouseclick on some switch in switch register */ static void srpress( struct box_rec *b ) { int bit = toggleswitch(b); int bitnum = b->state; sr = (sr & ~(04000 >> bitnum)) | (bit << (11-bitnum)); } /* mouseclick on load address switch */ static void addrloadpress( 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); } /* mouseclick on load extended address switch */ static void extdaddrloadpress( 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 ); } /* mouseclick on clear switch */ static void clearpress( struct box_rec *b ) { if (run == 0) { clearflags(); } bounceswitch( b ); } /* mouseclick on cont switch */ static void contpress( struct box_rec *b ) { if (run == 0) { run = 1; } bounceswitch( b ); } /* mouseclick on exam switch */ static void exampress( 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 ); } /* mouseclick on halt switch */ static void haltpress( struct box_rec *b ) { run = 0; bounceswitch( b ); } /* mouseclick on single step switch */ static void steppress( struct box_rec *b ) { run = 2; bounceswitch( b ); } /* mouseclick on deposit switch */ static void deppress( 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 ); } /* mouseclick on ok button in help message */ static void helpokpress( 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; #define h KC8Eheight /* 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 */ static void makeboxes() { 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 */ } decbox = 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 = device_name(); b.labx = i+s+p-t; b.press = backpress; b.help = device_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 = device_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; b.x = i+16*s-c-t; b.y = i+c; b.width = c-t; b.height = c-t; b.rule = circlebox; b.boxGC = blackGC; b.label = " "; b.labx = 0; b.laby = 0; b.press = backpress; b.help = "This blinks once per simulated second" " while the emulator runs"; b.next = sublist; b.sub = NOBOX; sublist = makebox(&b); heartbeat_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.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.press = helppress; b.help = "This emulator was written by" " Douglas W. Jones, dwjones@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 were 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); } panelbox = sublist; boxlist->sub = panelbox; /* 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" " (or press F1 - F12) to toggle a bit of 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); srboxes[x] = sublist; } } 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 i #undef p #undef c #undef t #undef s /********************************************/ /* Implementation of control panel function */ /********************************************/ static struct timer console_delay; static struct timer halt_delay; static void handle_button_press( Widget w, XtPointer d, XEvent *e, Boolean *cont ) { XButtonPressedEvent *be = (XButtonPressedEvent *)e; boxpress( be->x, be->y, be->button, boxlist ); } static void handle_button_release( Widget w, XtPointer d, XEvent *e, Boolean *cont ) { if (toggle != NOBOX) { /* handle undoing of button press */ bounceinternal(); } } /* this callback is public, since I/O devices with their own windows need it */ void handle_key_press( Widget w, XtPointer d, XEvent *e, Boolean *cont ) { XKeyPressedEvent *ke = (XKeyPressedEvent *)e; int shift = (ShiftMask & ke->state) != 0; int control = (ControlMask & ke->state) != 0; char ch; /* the ASCII character */ KeySym key = XkbKeycodeToKeysym( dpy, ke->keycode, 0, shift ); /* first deal with keys that map to ASCII, then handle others */ 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 if ((key >= XK_F1) && (key <= XK_F12)) { /* NOT ASCII */ /* accept F1-F12 as toggles to SR0 to SR11 */ srpress(srboxes[key - XK_F1]); return; /* ignore function keys after toggling SR */ } else { return; /* ignore all other non-ascii chars */ } if (keypress != NULL) { (* keypress)( ch ); /* e->x and e->y are not used */ } else { ttystuff( ch ); } } static void handle_exposure( Widget w, XtPointer d, XEvent *e, Boolean *cont ) { XExposeEvent *ee = (XExposeEvent *)e; if (ee->count == 0) { /* replot the whole thing */ boxshow( boxlist ); } } /* all update of console display happens here! */ static void console_event() { changeregs(); /* always display any changes to registers ! */ while (XtAppPending(app_context) || (run == 0)) { /* get console ops */ XEvent event; XtAppNextEvent(app_context, &event ); XtDispatchEvent( &event ); } if (run == 2) { /* single step */ schedule( &console_delay, console_interval, console_event, NOPARAM ); countdown -= (console_interval - 1); run = 0; } else { /* normal long step */ schedule( &console_delay, console_interval, console_event, NOPARAM ); } } /*********************************/ /* Window manager initialization */ /*********************************/ static XFontStruct* startfontsel( char * fonta, char * fontb, char * fontc ) { /* try to get a workable font */ static XFontStruct* f; if ((f = XLoadQueryFont( dpy, fonta )) == NULL) { if ((f = XLoadQueryFont( dpy, fontb )) == NULL) { if ((f = XLoadQueryFont( dpy, fontc )) == NULL) { ttyrestore(); exit( EXIT_FAILURE ); } } } return f; } void startwindow( int argc, char **argv ) { Arg arg[25]; XGCValues gcvalues; unsigned int n; /* * Set up the top level to house it all */ XtToolkitInitialize(); app_context = XtCreateApplicationContext(); dpy = XtOpenDisplay( app_context, NULL, NULL, "PDP8-E", NULL, 0, &argc, argv); scr = DefaultScreen(dpy); cmap = DefaultColormap(dpy, scr); /* * Get the fonts we need */ headinfo = startfontsel( HEADfont, HEADfonta, HEADfontb ); subinfo = startfontsel( SUBfont, SUBfonta, SUBfontb ); tinyinfo = startfontsel( TINYfont, TINYfonta, TINYfontb ); helpinfo = startfontsel( HELPfont, HELPfonta, HELPfontb ); /* * Create front-panel widget */ appshell = XtAppCreateShell( NULL, "PDP8-E", applicationShellWidgetClass, dpy, NULL, 0 ); XtAddCallback( appshell, XtNdestroyCallback, powercallback, NULL ); frontpanelshell = XtCreatePopupShell( progname, topLevelShellWidgetClass, appshell, arg, 0 ); n = 0; XtSetArg(arg[n], XtNwidth, KC8Eheight * 21/10); n++; XtSetArg(arg[n], XtNheight, KC8Eheight); n++; XtSetArg(arg[n], XtNbackground, BlackPixel( dpy, scr )); n++; frontpanel = XtCreateWidget( "panel", widgetClass, frontpanelshell, arg, n); XtManageChild(frontpanel); XtPopup(frontpanelshell, XtGrabNonexclusive); 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); /* what about DestroyNotify / XDestroyWindowEvent */ } /* call this from powerup in devices that need to manipulate windows */ void kc8getinfo( Display **d, int *s ) { *d = dpy; *s = scr; } /* call this from powerup in any device that needs its own windows */ Widget kc8makepopupshell( char *n ) { Arg arg[2]; return XtCreatePopupShell( n, topLevelShellWidgetClass, appshell, arg, 0 ); } /**********************************************************/ /* Interface between cpu implementation and control panel */ /**********************************************************/ /* power-on initialize */ void kc8power( int argc, char **argv ) { init_timer( console_delay ); schedule( &console_delay, console_interval, console_event, NOPARAM ); init_timer( heartbeat_delay ); schedule( &heartbeat_delay, heartbeat_interval, heartbeat, NOPARAM ); init_timer( halt_delay ); ttybreak = NULL; /* repeated control C has no effect */ startwindow( argc, argv ); first_device(); /* setup for walking through device list */ makeboxes(); } /* console reset */ void kc8init() { } /* respond to halt instruction */ void kc8halt() { /* force console event! */ schedule( &halt_delay, 0L, console_event, NOPARAM ); } xxxxxxxxxx cat > kc8m.c <<\xxxxxxxxxx /* File: kc8m.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 5, 2025 Language: C (UNIX) Purpose: DEC KC8/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 #include #include #include #include "realtime.h" #include "bus.h" #include "ttyaccess.h" #include "devices.h" #include "kk8e.h" #ifdef DEBUG #include "debug.h" #endif #include "kc8.h" #define control(ch) (ch & 037) /*********************/ /* utility functions */ /*********************/ /* convert num to octal to digits precision, followed by suffix text */ static void ttyoctal( int num, int digits, const char *suffix ) { char buf[32]; int temp = num; int i = digits; while (i > 0) { i--; buf[i] = (temp & 07) | '0'; temp >>= 3; } i = digits; int j = 0; while (suffix[j] != '\0') { buf[i++] = suffix[j++]; } buf[i] = '\0'; ttyputs(buf); } /* callback passed to foralldevices, used by D (list_devices) command */ static void listadevice( char *sname, char *lname, char *fname ) { ttyputs( sname ); ttyputs( " (" ); ttyputs( lname ); ttyputs( "): " ); ttyputs( fname ); ttyputs( "\r\n" ); } /* list all mounted devices so user can mount or dismount */ static void list_devices() { ttyputs( "\r\n" ); foralldevices( listadevice ); } /********************************************/ /* Implementation of control panel function */ /********************************************/ static struct timer console_delay; static const char * help_message = "\r\n PDP-8/E emulator, commands are:\r\n\n" " Q = quit PDP-8 ^C^C^C^C^C = halt cpu\r\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" " = close unchanged nnnn = store N in open loc, close\r\n" " value in location is printed when location opened.\r\n\n" #ifdef DEBUG " H = print instruction and memory history since last H\r\n" #endif " D = list all devices\r\n" " M = mount file on device\r\n" " to dismount file, mount nothing on the device\r\n\n"; /* all console functions happen here! */ static void console_event() { 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'))||(ch == control('@'))); 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( (linkbit >> 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) { linkbit = (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 */ { char n[5]; char f[NAME_LENGTH]; ttyputs( "\r\n device: " ); ttygets( n, 5 ); if (get_device(n)) { /* named device exists */ ttyputs( " file: " ); ttygets( f, NAME_LENGTH ); if (f[0] != '\0') { strncpy( device_file(), f, NAME_LENGTH-1 ); if (!device_mount()) { ttyputs( "?\r\n" ); } } else { device_dismount(); } } 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 */ /**********************************************************/ /* called by keyboard server to get attention */ static void hitbreak() { run = 0; schedule( &console_delay, 0L, console_event, 0 ); } /* power-on initialize */ void kc8power(int argc, char** argv) { init_timer( console_delay ); ttybreak = hitbreak; ttyputs( "PDP-8 Emulator, type ? for help.\r\n" ); } /* console reset */ void kc8init() { /* nothing to do here */ } /* respond to halt instruction */ void kc8halt() { /* force console event! */ schedule( &console_delay, 0L, console_event, 0 ); } xxxxxxxxxx cat > km8e.h <<\xxxxxxxxxx /* File: km8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Jul 29, 2025 Language: C (UNIX) Purpose: interface to DEC KM8/E memory management functions, excepting those included in the KK8E cpu. */ /******************/ /* Initialization */ /******************/ /* console reset */ void km8einit(); /********************/ /* IOT Instructions */ /********************/ void km8edev( int op ); xxxxxxxxxx cat > km8e.c <<\xxxxxxxxxx /* File: km8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Jul 29, 2025 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" #include "km8e.h" /******************/ /* Initialization */ /******************/ /* global initialize */ void km8epower() { ifr = 0; ib = 0; dfr = 0; sf = 0; uf = 0; ub = 0; } /* console reset */ void km8einit() { km8e_uif = 0; /* reset to user mode eventually */ } /********************/ /* IOT Instructions */ /********************/ void km8edev( 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 > dk8e.h <<\xxxxxxxxxx /* File: dk8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC DK8/EA or /EC real time clock emulator, */ /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* power-on initialize */ void dk8epower(); /* console reset */ void dk8einit(); /********************/ /* IOT Instructions */ /********************/ void dk8edev( int op ); xxxxxxxxxx cat > dk8e.c <<\xxxxxxxxxx /* File: dk8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 7, 2025 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" #include "dk8e.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; /* set by clock tick, reset by user */ static int interrupt_enable; /* we are allowed to interrupt */ /**********************/ /* extra hidden state */ /**********************/ static int irq_count; /* 0 or 1, to prevent miscounting interrupts */ /*************************/ /* Device implementation */ /*************************/ /* called from timer when a the real-time clock ticks */ static void tick_event( int p ) { if (tick_flag == 0) { /* clock ticks are no-ops if the flag is already set */ tick_flag = 1; if ((interrupt_enable == 1) && (irq_count == 0)) { irq = irq + 1; irq_count = 1; } } schedule( &tick_delay, tick_time, &tick_event, NOPARAM ); } /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* power-on initialize */ void dk8epower() { init_timer(tick_delay); schedule( &tick_delay, tick_time / 2, &tick_event, NOPARAM ); irq_count = 0; } /* console reset */ void dk8einit() { if (irq_count > 0) { /* retract interrupt request */ irq_count = 0; irq = irq - 1; } tick_flag = 0; interrupt_enable = 0; /* assume that cpu clears irq for us */ } /********************/ /* IOT Instructions */ /********************/ void dk8edev( int op ) { switch (op) { case 00: /* no operation */ break; case 01: /* CLEI */ if (interrupt_enable == 0) { if ((tick_flag == 1) && (irq_count == 0)) { irq = irq + 1; irq_count = 1; } interrupt_enable = 1; } break; case 02: /* CLDI also called CLED */ if (interrupt_enable == 1) { if (irq_count == 1) { /* retract interrupt request */ irq_count = 0; irq = irq - 1; } interrupt_enable = 0; } break; case 03: /* CLSK */ if (tick_flag == 1) { pc = pc + 1; if (irq_count > 0) { /* retract interrupt request */ irq_count = 0; irq = irq - 1; } tick_flag = 0; } break; case 04: /* no operation! */ case 05: /* no operation! */ case 06: /* no operation! */ case 07: /* no operation! */ break; } } xxxxxxxxxx cat > debug.h <<\xxxxxxxxxx /* File: debug.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to PDP-8 emulator debugging tool */ /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* power-on initialize */ void reset_debug(); /************************/ /* Accumulate histogram */ /************************/ /* record that instruction m was fetched from location p */ void accumulate_debug( int p, int m ); /************************/ /* Interface to console */ /************************/ /* show histogram */ void output_debug(); xxxxxxxxxx cat > debug.c <<\xxxxxxxxxx /* File: debug.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: PDP-8 emulator debugging tool */ #include #include "bus.h" #include "ttyaccess.h" /***********************************/ /* instruction frequency histogram */ /***********************************/ static int instructions[4096]; static int locations[MAXMEM]; /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* power-on initialize */ void reset_debug() { int i; for (i=0; i<4096; i++) { instructions[i] = 0; } for (i=0; 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 0) { sprintf( buf, " %05o: /%6d\r\n", i, locations[i]); ttyputs( buf ); } } reset_debug(); } xxxxxxxxxx cat > kl8e.h <<\xxxxxxxxxx /* File: kl8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC KL8/E console teletype emulator */ /******************/ /* Initialization */ /******************/ /* global initialize */ void kl8epower(); /* console reset */ void kl8einit(); /********************/ /* IOT Instructions */ /********************/ void kl8edev3( int op ); void kl8edev4( int op ); xxxxxxxxxx cat > kl8e.c <<\xxxxxxxxxx /* File: kl8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 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 #include "realtime.h" #include "bus.h" #include "ttyaccess.h" #include "kl8e.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 */ /*************************/ /* called to poll for keyboard input */ static void keyboard_event( 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 ); } /* called to enable keyboard_event when reading from tape */ static void read_character() { /* right now, tape is not supported, so this does nothing */ } /* called from timer when a byte has been successfully printed */ static void print_event( int p ) { /* 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; } } /* schedule the completion of a print "print_time" in the future */ static void print_character() { schedule( &print_delay, print_time, print_event, 0 ); } /******************/ /* Initialization */ /******************/ /* global initialize */ void kl8epower() { /* 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 ); } /* console reset */ void kl8einit() { keyboard_flag = 0; print_flag = 0; interrupt_enable = 1; } /********************/ /* IOT Instructions */ /********************/ void kl8edev3( 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; } } void kl8edev4( 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 > pc8e.h <<\xxxxxxxxxx /* File: pc8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC PC8/E high speed paper tape reader-punch emulator, */ /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* power-on initialize */ void pc8epower(); /* console reset */ void pc8einit(); /********************/ /* IOT Instructions */ /********************/ void pc8edev1( int op ); void pc8edev2( int op ); xxxxxxxxxx cat > pc8e.c <<\xxxxxxxxxx /* File: pc8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 5, 2025 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 #include #include "realtime.h" #include "bus.h" #include "utility.h" #include "devices.h" #include "pc8e.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 void readerclose( int u ) { if (reader_stream != NULL) { fclose( reader_stream ); reader_stream = NULL; readername[0] = '\0'; } } static bool readeropen( 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 void punchclose( int u ) { if (punch_stream != NULL) { fclose( punch_stream ); punch_stream = NULL; punchname[0] = '\0'; } } static bool punchopen( 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); } /* power-on initialize */ void pc8epower() { 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 */ /*************************/ /* called from timer when a byte has been successfully read */ static void reader_event( int p ) { 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 */ } if (interrupt_enable == 1) { irq = (irq - reader_flag) + 1; } reader_flag = 1; /* signal that read is complete */ } /* schedule the completion of a read "reader_time" in the future */ static void read_character() { schedule( &reader_delay, reader_time, reader_event, 0 ); } /* called from timer when a byte has been successfully printed */ static void punch_event( int p ) { if (punch_stream != NULL) { putc(punch_buffer, punch_stream); } else { /* simulate punching with no tape in punch */ } if (interrupt_enable == 1) { irq = (irq - punch_flag) + 1; } punch_flag = 1; } static void 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 */ /***********************************************/ /* console reset */ void pc8einit() { punch_flag = 0; reader_flag = 0; interrupt_enable = 1; /* assume that cpu clears irq for us */ } /********************/ /* IOT Instructions */ /********************/ void pc8edev1( 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; reader_flag = 0; 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; } } void pc8edev2( 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 > cr8f.h <<\xxxxxxxxxx /* File: cr8f.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: Interface to DEC CR8-F Card Reader and Control */ /******************/ /* Initialization */ /******************/ /* global initialize */ void cr8fpower(); /* console reset */ void cr8finit(); /********************/ /* IOT Instructions */ /********************/ void cr8fdev3( int op ); void cr8fdev7( int op ); xxxxxxxxxx cat > cr8f.c <<\xxxxxxxxxx /* File: cr8f.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. CoAuthor: Satish Viswanatham Date: Aug 5, 2025 Language: C (UNIX) Purpose: DEC CR8-F Card Reader and Control Based on the description in the Small Computer Handbook, Digital Equipment Corporation, 1973, plus inspection of the schematics in the PDP-8/E/F/M maintenance manual. */ #include #include #include "realtime.h" #include "bus.h" #include "utility.h" #include "devices.h" #include "cr8f.h" #define read_time ( 1 * millisecond/ IOFUDGE) #define pick_lead_time ( 46 * millisecond/ IOFUDGE) #define trail_time ( 23 * millisecond/ IOFUDGE) /*************************************/ /* "officially visible" device state */ /*************************************/ static int read_buffer; static int data_ready_flag; static int card_done_flag; static int ready_true_trans_flag; static int trouble_trans_flag; static int true_trouble_enable; static int ready_done_enable; /**********************/ /* other device state */ /**********************/ static int cur_column; /* current card column */ static int byte1, byte2, byte3; /* three consecutive bytes of input */ /*********************************************************/ /* Interface between device implementation and "console" */ /*********************************************************/ /* timers used to simulate delays between I/O initiation and completion */ static struct timer read_delay; static FILE *reader_stream = NULL; static char cardname[NAME_LENGTH]; static void cardclose( int u ) { if (reader_stream != NULL){ fclose(reader_stream); reader_stream = NULL; cardname[0] = '\0'; /* putting reader off-line requests interrupt if enabled */ if (true_trouble_enable) { irq = irq - ready_true_trans_flag + 1; } /* putting reader off-line sets flags */ ready_true_trans_flag = 0; trouble_trans_flag = 1; } } static bool cardopen( int u, char *f ) { cardclose( u ); set_file_name( cardname, f ); if (( reader_stream = fopen(cardname, "r")) == NULL){ /* fail */ cardname[0] = '\0'; } else { /* success */ /* check for prefix on input file */ if ((fgetc( reader_stream ) != 'H') || (fgetc( reader_stream ) != '8') || (fgetc( reader_stream ) != '0')) { fclose(reader_stream); reader_stream = NULL; cardname[0] = '\0'; } else { /* putting reader on-line may request interrupt */ if (true_trouble_enable) { irq = irq - trouble_trans_flag + 1; } /* putting reader on-line sets flags */ ready_true_trans_flag = 1; trouble_trans_flag = 0; } } return (reader_stream != NULL); } /*************************/ /* Device Implementation */ /*************************/ static void end_of_card( int p ) { if (ready_done_enable) { irq = irq - card_done_flag + 1; } card_done_flag = 1; if (feof(reader_stream)) { cardclose(0); } } static void read_odd_column( int p ); /* forward declaration */ static void read_even_column( int p ) { byte1 = fgetc(reader_stream); byte2 = fgetc(reader_stream); byte3 = fgetc(reader_stream); read_buffer = (byte1 << 4) | (byte2 >> 4); cur_column++; if (ready_done_enable) { irq = irq - data_ready_flag + 1; } data_ready_flag = 1; /* note that even columns never end cards! */ schedule( &read_delay, read_time, read_odd_column, NOPARAM ); } static void read_odd_column( int p ) { read_buffer = ((byte2 & 0017) << 8) | byte3; cur_column++; if (ready_done_enable) { irq = irq - data_ready_flag + 1; } data_ready_flag = 1; if ( cur_column >= 80){ /* end of card */ schedule( &read_delay, trail_time, end_of_card, NOPARAM ); } else { schedule( &read_delay, read_time, read_even_column, NOPARAM ); } } /***********************/ /* Data Conversion */ /***********************/ /* map 12 bit card codes to 8 bit "compressed codes" */ char compressed_code[4096]; /* map 12 bit card codes to 6 bit "holerith codes" (truncated ASCII) */ char hollerith_code[4096]; /* initialize conversion tables */ void init_conversions() { int i; /* go through all possible 12 bit codes */ /* card column numbering: 12 11 0 1 2 3 4 5 6 7 8 9 */ /* bit numbering: 0 1 2 3 4 5 6 7 8 9 10 11 */ for (i = 0; i < 4096; i++) { /* this material used for error detection in compressed code */ int bitcount; bitcount = (i & 00774) >> 2; /* extract columns 1-7 */ /* the following 3 lines count the 1 bits in columns 1-7 */ bitcount = (bitcount & 0125) + ((bitcount & 0052) >> 1); bitcount = (bitcount & 0063) + ((bitcount & 0114) >> 2); bitcount = (bitcount & 0017) + ((bitcount & 0160) >> 4); compressed_code[ i ] = /* rearrange bits */ ((i & 00001) << 7) /* row 9 goes in bit 4 */ | ((i & 07000) >> 5) /* rows 12-11-10 go in bits 5 to 7 */ | ((i & 00002) << 2) /* row 8 goes in bit 8 */ /* encode remaining numeric punches */ | ((i & 00400) >> 8) /* row 1 sets bit 11 */ | ((i & 00200) >> 6) /* row 2 sets bit 10 */ | ((i & 00100) >> 5) /* row 3 sets both 10 */ | ((i & 00100) >> 6) /* and 11*/ | ((i & 00040) >> 3) /* row 4 sets bit 9 */ | ((i & 00020) >> 2) /* row 5 sets both 9 */ | ((i & 00020) >> 4) /* and 11 */ | ((i & 00010) >> 1) /* row 6 sets both 9 */ | ((i & 00010) >> 2) /* and 10 */ | ((i & 00004) ) /* row 7 sets 9 */ | ((i & 00004) >> 1) /* and 10 */ | ((i & 00004) >> 2) /* and 11*/ /* detect errors in numeric punches */ | ((bitcount > 1) << 11); hollerith_code[ i ] = /* encode zone */ ((i & 03000) >> 5) /* rows 11,0 go in bits 6,7 */ | ((i & 04000) >> 6) /* row 12 sets both 6 */ | ((i & 04000) >> 7) /* and 7 */ /* encode numeric punches */ | ((i & 00400) >> 8) /* row 1 sets bit 11 */ | ((i & 00200) >> 6) /* row 2 sets bit 10 */ | ((i & 00100) >> 5) /* row 3 sets both 10 */ | ((i & 00100) >> 6) /* and 11*/ | ((i & 00040) >> 3) /* row 4 sets bit 9 */ | ((i & 00020) >> 2) /* row 5 sets both 9 */ | ((i & 00020) >> 4) /* and 11 */ | ((i & 00010) >> 1) /* row 6 sets both 9 */ | ((i & 00010) >> 2) /* and 10 */ | ((i & 00004) ) /* row 7 sets 9 */ | ((i & 00004) >> 1) /* and 10 */ | ((i & 00004) >> 2) /* and 11*/ | ((i & 00002) << 2) /* row 8 sets bit 8 */ | ((i & 00001) << 3) /* row 9 sets bit 8 */ | ((i & 00001) );/* and 11 */ } } /******************/ /* Initialization */ /******************/ /* global initialize */ void cr8fpower() { /* set up timers used to delay I/O activity */ init_timer(read_delay); init_conversions(); cardname[0] = '\0'; /* file not mounted yet */ register_device( cardopen, cardclose, 0, "CDR", "-- Card reader ", cardname); } /* console reset */ void cr8finit() { true_trouble_enable = 0; ready_done_enable = 1; ready_true_trans_flag = 0; trouble_trans_flag = 0; data_ready_flag = 0; card_done_flag = 0; } /********************/ /* IOT Instructions */ /********************/ void cr8fdev3( int op ) { switch (op) { case 01: /* RCSF */ if (data_ready_flag == 1) { pc = (pc + 1) & 07777; } break; case 02: /* RCRA */ ac = hollerith_code[read_buffer]; if (ready_done_enable == 1) { irq = irq - data_ready_flag; } data_ready_flag = 0; break; case 03: /* no operation! */ break; case 04: /* RCRB */ ac = read_buffer; if (ready_done_enable == 1) { irq = irq - data_ready_flag; } data_ready_flag = 0; break; case 05: /* RCNO */ if (((ac & 00002) != 0) && (true_trouble_enable == 0)) { true_trouble_enable = 1; irq = irq + ready_true_trans_flag + trouble_trans_flag; } if (((ac & 00001) == 04000 ) && (ready_done_enable == 0)) { ready_done_enable = 1; irq = irq + card_done_flag + data_ready_flag; } break; case 06: /* RCRC */ ac = compressed_code[read_buffer]; if (ready_done_enable == 1) { irq = irq - data_ready_flag; } data_ready_flag = 0; break; case 07: /* RCNI */ ac = (ready_true_trans_flag << 8) | (trouble_trans_flag << 9) | (card_done_flag << 10) | (data_ready_flag << 11); break; } } void cr8fdev7( int op ) { switch (op) { case 01: /* RCSD */ if (card_done_flag == 1) { pc = (pc +1) & 07777; } break; case 02: /* RCSE */ if (reader_stream != NULL) { /* reader on-line */ /* get header of next card */ byte1 = fgetc(reader_stream); byte2 = fgetc(reader_stream); byte3 = fgetc(reader_stream); if ((byte1==EOF) /* normal EOF */ || (byte2==EOF) /* truncated file? */ || (byte3==EOF) /* truncated file? */ || ((byte1 & 0x80)==0) /* corrupt file */ || ((byte2 & 0x80)==0) /* corrupt file */ || ((byte3 & 0x80)==0) /* corrupt file */ ) { /* declare the reader off line */ cardclose(0); } else { /* a card is available */ pc = (pc +1) & 07777; if (ready_done_enable == 1) { irq = irq - card_done_flag; } card_done_flag = 0; cur_column = 0; schedule( &read_delay, pick_lead_time, read_even_column, NOPARAM ); } } break; case 03: /* no operation! */ break; case 04: /* RCRD */ if (ready_done_enable == 1) { irq = irq - card_done_flag; } card_done_flag = 0; break; case 05: /* RCSI */ if ( ( true_trouble_enable && (ready_true_trans_flag || trouble_trans_flag) ) || ( ready_done_enable && (data_ready_flag || card_done_flag) ) ) { pc = (pc + 1) & 07777; } break; case 06: /* no operation! */ break; case 07: /* RCTF*/ if (true_trouble_enable == 1) { irq = irq - ready_true_trans_flag +-trouble_trans_flag; } ready_true_trans_flag = 0; trouble_trans_flag = 0; break; } } xxxxxxxxxx cat > rx8e.h <<\xxxxxxxxxx /* File: rx8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Jul 29, 2025 Language: C (UNIX) Purpose: interface to DEC RX8 RX01 diskette interface */ /* power-on initialize */ void rx8epower(); /***********************************************/ /* Initialization used by CAF and reset switch */ /***********************************************/ /* console reset or programmed reset */ void rx8einit(); /********************/ /* IOT Instructions */ /********************/ void rx8edev( int op ); xxxxxxxxxx cat > rx8e.c <<\xxxxxxxxxx /* File: rx8e.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 5, 2025 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 #include #include "realtime.h" #include "bus.h" #include "utility.h" #include "devices.h" #include "rx8e.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 void diskclose( 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 bool diskopen( 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); } /* power-on initialize */ void rx8epower() { 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; /* called from within rx01_event() to indicate finishing of a rx01 operation */ static void rx01_done() { done_flag = 1; rx01_function = rx01_idle; if (interrupt_enable == 1) { irq = irq + 1; } interface_register = RXES; } /* called from timer as each sector spins by disk heads */ static void rx01_event( int p ) { 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, NOPARAM ); } /* 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 */ /* called from withing rx8e_event() to indicate end of an rx8 operation */ static void rx8_done() { done_flag = 1; partial = 0; bufpos = 0; if (interrupt_enable == 1) { irq = irq + 1; } interface_register = RXES; } /* called from timer when an interface delay has completed */ static void rx8e_event( int p ) { 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 */ /***********************************************/ /* console reset or programmed reset */ void rx8einit() { 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, NOPARAM ); /* load heads and wait for them to bounce */ schedule( &rx8e_delay, settle_time, rx8e_event, rx8_init ); } /********************/ /* IOT Instructions */ /********************/ void rx8edev( 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 > vc8e.c <<\xxxxxxxxxx /* File: vc8e.c Date: Aug. 12, 2025 Language: C (UNIX) Purpose: DEC oscilloscope output emulator Based on the description in the PDP-8/E Small Computer Handbook, Digital Equipment Corporation, 1971. WARNING -- this description is incomplete, and therefore, the implementation given here is likely to be wrong! It would be helpful if I could find the M885/M869 schematics or the VC8E maintenance manual. */ #include #include #include #include #include #include #include #include #include "realtime.h" #include "bus.h" #include "kc8e.h" #include "vc8e.h" /******************************************************************/ /* options: The user may select any of a the following displays: */ /* VR14, VR20, TEK602, TEK611 */ /* If the VR14 display is selected, the old version of the */ /* interface will be emulated (pre M869 Rev D -- M885 Rev F). */ /* */ /* The user may select how big an X-window is used to emulate the */ /* display. All real displays had 1024 x 1024 resolution. Using */ /* smaller windows saves memory and screen space. */ /* */ /* Note: The multi-channel scope option is not supported! */ /******************************************************************/ /* Define the display type */ #define VR14 /* translations from display resolution to scale factor */ #define RES_1024 0 #define RES_512 1 #define RES_256 2 #define RES_128 3 /* select a resolution */ #define PIX_SCALE RES_512 /**********************************************************************/ /* derivation of actual display parameters from user selected options */ /**********************************************************************/ #if defined(VR14) /* basic point-plot display */ #define CRT "VR14" #define SETTLING_INTERVAL (21*microsecond) #define STATUS_MASK 00003 #endif #if defined(VR20) /* two color point-plot display */ #define CRT "VR20" #define SETTLING_INTERVAL (21*microsecond) #define STATUS_MASK 00077 #endif #if defined(TEK602) /* basic point-plot display */ #define CRT "TEK602" #define SETTLING_INTERVAL (6*microsecond) #define STATUS_MASK 00077 #endif #if defined(TEK611) /* storage scope display */ #define CRT "TEK611" #define SETTLING_INTERVAL (6*microsecond) #define STATUS_MASK 00077 #endif #if !defined(CRT) error misconfigured display options #endif /* PIXELS gives the screen dimension in displayed pixels */ #define PIXELS (1 << (10 - PIX_SCALE)) /* persistance of the phosphor on the screen (about the flicker fusion time) */ #define PERSISTANCE ( 60 * millisecond ) /* number of points that must be remembered so they can be erased later */ /* the factor of 2 is a safety margin in case software doesn't wait for done */ #define MAX_POINTS ( 2 * ( PERSISTANCE / SETTLING_INTERVAL )) /******************/ /* Interface to X */ /******************/ static Display* dpy; /* its display */ static int scr; /* its screen */ static Colormap cmap; /* its colormap */ static Widget crtshell; /* the X window shell */ static Widget crt; /* the X window in which output will plot */ static XColor beam; /* color for for CRT beam */ #if defined(VR20) static XColor red; /* color for red CRT beam */ #endif static GC whiteGC; /* gc with white foreground */ static GC blackGC; /* gc with black foreground */ static GC beamGC; /* gc with color of CRT beam foreground */ static GC redGC; /* gc with for VR20 color CRT foreground */ static GC greenGC; /* gc with for VR20 color CRT foreground */ static void init_x() { Arg arg[25]; XGCValues gcvalues; unsigned int n; kc8getinfo( &dpy, &scr ); cmap = DefaultColormap(dpy, scr); crtshell = kc8makepopupshell( CRT ); /* * Create a drawing area */ n = 0; XtSetArg(arg[n], XtNwidth, PIXELS); n++; XtSetArg(arg[n], XtNheight, PIXELS); n++; XtSetArg(arg[n], XtNbackground, BlackPixel( dpy, scr )); n++; crt = XtCreateWidget( CRT, widgetClass, crtshell, arg, n); XtManageChild(crt); XtPopup(crtshell, XtGrabNonexclusive); /* * Create black and white Graphics Contexts */ gcvalues.foreground = BlackPixel( dpy, scr ); gcvalues.background = BlackPixel( dpy, scr ); blackGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); gcvalues.foreground = WhitePixel( dpy, scr ); whiteGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); /* * Create monochrome Graphics Contexts */ beam.red = 128*256; beam.green = 255*256; /* a bright green shade? */ beam.blue = 128*256; if ( XAllocColor( dpy, cmap, &beam ) && (beam.pixel != WhitePixel( dpy, scr )) ) { /* we got the color we want! */ gcvalues.foreground = beam.pixel; beamGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); } else { /* fake it as white if we can't get that color */ beam.pixel = WhitePixel( dpy, scr ); beamGC = whiteGC; } /* set default colors in case this isn't a VR20 */ greenGC = beamGC; redGC = beamGC; #if defined(VR20) /* we need the another color */ red.red = 255*256; red.green = 96*256; /* a red shade? */ red.blue = 96*256; if ( XAllocColor( dpy, cmap, &red ) && (red.pixel != WhitePixel( dpy, scr )) ) { /* we got the color we want! */ gcvalues.foreground = red.pixel; redGC = XCreateGC(dpy, XtWindow(crt), GCForeground | GCBackground, &gcvalues); } else { /* fake it as white if we can't get that color */ red.pixel = WhitePixel( dpy, scr ); redGC = whiteGC; } #endif XtAddEventHandler( crt, KeyPressMask, FALSE, handle_key_press, NULL ); } /* put a point on the screen */ static void display_point( int x, int y ) { XDrawPoint(dpy, XtWindow(crt), beamGC, x, PIXELS-y); } /* remove a point from the screen */ static void reset_point( int x, int y ) { XDrawPoint(dpy, XtWindow(crt), blackGC, x, PIXELS-y); } /* flood the screen with color c */ static void flood_screen( GC c ) { XFillRectangle(dpy, XtWindow(crt), c, 0, 0, PIXELS, PIXELS ); } /*************************************/ /* "officially visible" device state */ /*************************************/ static int x_reg = 0; static int y_reg = 0; static int enab_stat_reg; /* status bits and fields */ #define done_flag 04000 #define write_through 00040 #define store 00020 #define erase 00010 #define color 00004 #define channel_no 00002 #define interrupt 00001 /*********************************/ /* Display Device Implementation */ /*********************************/ /* FIFO queue of points to erase when they decay */ typedef struct point{ short int x, y; /* coordinates of point */ long int delay; /* time from previous point to this point */ } Point; static Point point_queue[MAX_POINTS]; static int tail; /* enqueue pointer */ static int head; /* dequeue pointer */ long int queue_interval; /* total time interval between head and tail */ /* array counting how many times each pixel is listed in the point_queue */ static short int point_count[PIXELS][PIXELS]; /* timer used to simulate decay of points on the screen */ static struct timer plot_timer; /* time until next plotted point winks out */ static void remove_point() { int x,y; x = point_queue[head].x; y = point_queue[head].y; if ( point_count[x][y] <= 1) { reset_point(x,y); point_count[x][y] = 0; } else { point_count[x][y]--; } head = (head + 1) % MAX_POINTS; if ( head != tail ){ queue_interval -= point_queue[head].delay; schedule( &plot_timer, point_queue[head].delay, remove_point, NOPARAM); } else { queue_interval = 0; } } static void add_point() { int x,y; /* recall that x and y values are signed; this converts them to unsigned values, origin in lower left */ x = (x_reg & 01777) ^ 01000; y = (y_reg & 01777) ^ 01000; /* scale x and y to the displayed number of pixels */ x = x >> PIX_SCALE; y = y >> PIX_SCALE; /* x and y are now in the range 0..(PIXELS-1) */ display_point(x, y); #if defined(TEK611) /* storage scope behavior */ if (enab_stat_reg & store) { /* poor fake of write_through mode */ if (!(enab_stat_reg & write_through)) { point_count[x][y]++; return; } } #endif /* recall that this point has been displayed */ point_count[x][y]++; /* enqueue this point for deletion */ point_queue[tail].x = x; point_queue[tail].y = y; if(head == tail){ /* empty list, therefore timer is not set */ schedule( &plot_timer, PERSISTANCE, remove_point, NOPARAM); queue_interval = 0; } else { long int d; d = PERSISTANCE - (query_timer( &plot_timer ) + queue_interval); point_queue[tail].delay = d; queue_interval += d; } /* note that, for software that ignores the done bit, the following code could overrun the head pointer */ tail = (tail + 1) % MAX_POINTS; } /**********************************/ /* internal behavior of interface */ /**********************************/ static struct timer dilx_timer; /* time delay for dilx */ static struct timer dily_timer; /* time delay for dily */ static struct timer VR20_timer; /* time delay for color change */ static struct timer TEK_timer; /* time delay for erase pulse */ static int done_timer_count; static void setdoneflag( int p ) { /* only set done if all done-timers are expired */ if (done_timer_count > 0) { done_timer_count--; } if (done_timer_count == 0) { if (!(enab_stat_reg & done_flag)) { enab_stat_reg |= done_flag; if (enab_stat_reg & interrupt) { irq = irq + 1; } } } } static void enderase( int p ) { int i, j; for(i =0; i < PIXELS; i++) for( j=0; j < PIXELS; j++) point_count[i][j] = 0; flood_screen( blackGC ); /* this here? ---- ac = enab_stat_reg & ~erase; ---*/ setdoneflag(p); } static void resetdoneflag() { if (enab_stat_reg & done_flag) { enab_stat_reg &= ~done_flag; if (enab_stat_reg & interrupt) { irq = irq + 1; } } } /******************/ /* Initialization */ /******************/ /* global initialize */ void vc8epower() { int i,j; /* Initializing display list to NULL */ init_timer(plot_timer); head = 0; tail = 0; for(i =0; i < PIXELS; i++) for( j=0; j < PIXELS; j++) point_count[i][j] = 0; /* Initializing timers for delays until done to NULL */ init_timer(dilx_timer); init_timer(dily_timer); init_timer(VR20_timer); init_timer(TEK_timer); done_timer_count = 0; /* Do X initialization */ init_x(); } /* console reset */ void vc8einit() { enab_stat_reg = 0; } /********************/ /* IOT Instructions */ /********************/ void vc8edev5( int op ) { switch (op) { case 00: /* DILC */ enab_stat_reg = 0; break; case 01: /* DICD */ resetdoneflag(); break; case 02: /* DISD */ if (enab_stat_reg & done_flag) { pc = (pc+1) & 07777; } break; case 03: /* DILX */ resetdoneflag(); x_reg = ac & 01777; /* 10 bits only! */ /* wait for to settle */ done_timer_count++; schedule(&dilx_timer, SETTLING_INTERVAL, setdoneflag, NOPARAM); break; case 04: /* DILY */ resetdoneflag(); y_reg = ac & 01777; /* 10 bits only! */ /* wait for to settle */ done_timer_count++; schedule(&dily_timer, SETTLING_INTERVAL, setdoneflag, NOPARAM); break; case 05: /* DIXY */ resetdoneflag(); /* intensify */ add_point(); /* no delay needed here, intensify pulse is 1 microsec */ done_timer_count++; setdoneflag(1); break; case 06: { /* DILE */ int old_stat = enab_stat_reg; enab_stat_reg |= ac & STATUS_MASK; /* things that cause delays are tested in order of increasing delay in order to get the maximum of all the conditions that may cause any delay */ if ((old_stat ^ enab_stat_reg) & color) { /* -- VR20 color change -- */ long int delay; /* is this needed? ---- resetdoneflag() */ if (enab_stat_reg & color) { /* green to red */ beamGC = redGC; delay = 300 * microsecond; } else { /* red to green */ beamGC = greenGC; delay = 1600 * microsecond; } done_timer_count++; schedule( &VR20_timer, delay, setdoneflag, NOPARAM); } if (enab_stat_reg & erase) { /* -- erase Tex storage scope screen -- */ long int delay; /* is this needed? ---- resetdoneflag() */ flood_screen( beamGC ); ac = enab_stat_reg & ~erase; /*this here---*/ delay = 50 * millisecond; done_timer_count++; schedule(&TEK_timer, delay, enderase, NOPARAM); } ac = 00000; } break; case 07: /* DIRE */ ac = enab_stat_reg; break; } } xxxxxxxxxx cat > vc8e.h <<\xxxxxxxxxx /* File: vc8e.h Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC oscilloscope output emulator */ /******************/ /* Initialization */ /******************/ /* global initialize */ void vc8epower(); /* console reset */ void vc8einit(); /********************/ /* IOT Instructions */ /********************/ void vc8edev5( int op ); xxxxxxxxxx cat > utility.c <<\xxxxxxxxxx /* File: utility.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: DEC PDP-8/e emulator utility routines */ #include #include #include #include #include "bus.h" #include "kc8.h" #include "devices.h" #include "ttyaccess.h" #include "utility.h" /* The following routine is used to make a copy of a string s into a buffer * * that will be used as a file name f. 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. */ void set_file_name( char *f, char *s ) { int i = 0; /* index into f */ int j = 0; /* index into s */ while ((i < (NAME_LENGTH - 1)) && (isgraph(s[j]))) { f[i++] = s[j++]; } f[i] = '\0'; } /* command line argument processing */ void getargs( 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( EXIT_FAILURE ); } else if (corename[0] != '\0') { fprintf( stderr, "%s: too many core files specified\n", progname ); exit( EXIT_FAILURE ); } 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 * * "#!", where "" 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. * * * * optionally, lines formatted "m dev file" may be appended to mount the * * named file on the named device. */ /* used only in readcore to report trouble */ static void readcorefailure( char *thing, char *reason ) { ttyrestore(); fprintf( stderr, "%s: %s %s\n", progname, thing, reason ); exit( EXIT_FAILURE ); } /* read core image file core */ void 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) { readcorefailure( corename, "cannot read core image file" ); } if ((c = getc(f)) == '#') { /* skip emulator prefix */; do { /* skip until eol or eof */ c = getc(f); } while ((c != EOF) && (c != '\n')); } c = getc(f); 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; } /* comes here on (c>=a) or (c==EOF) or (a>=MAXMEM) */ if ((c != EOF) && (c < 'a')) { readcorefailure( corename, "memory too small for core image" ); } if (c != EOF) { /* try to read epilogue */ char n[5]; /* a device name */ char fn[NAME_LENGTH]; /* a file name */ int i; while (c == 'm') { /* try to mount a file */ c = getc(f); if (c != ' ') break; i = 0; /* setup to read device name */ c = getc(f); while (isgraph(c)) { /* get device name */ n[i++] = c; c = getc(f); if (i >= 4) break; } n[i] = '\0'; if (c != ' ') break; i = 0; /* setup to read name of file*/ c = getc(f); while (isgraph(c)) { /* get file name */ fn[i++] = c; c = getc(f); if (i >= (NAME_LENGTH-1)) break; } fn[i] = '\0'; if (c != '\n') break; c = getc(f); if (get_device( n )) { char *nfile = device_file(); if (*nfile != '\0') { readcorefailure( n, "mounted twice" ); } set_file_name( device_file(), fn ); if (!device_mount()) { readcorefailure( fn, "cannot mount" ); } } else { readcorefailure( n, "device not configured" ); } } fclose(f); } } /* callback used only by dumpcore to output a device binding */ static FILE *dumpfile; static void dumpdevice( char *sname, char *lname, char *fname ) { fputs( "m ", dumpfile ); fputs( sname, dumpfile ); fputs( " ", dumpfile ); fputs( fname, dumpfile ); fputs( "\n", dumpfile ); } /* output a dump of core and a list of device bindings */ void dumpcore() { int a, max; /* memory address */ int d; /* count of characters on this line */ if ((dumpfile = 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( "#!", dumpfile ); fputs( PDP8NAME, dumpfile ); putc( '\n', dumpfile ); d = 0; max = MAXMEM-1; while (memory[max] == 0) { /* avoid output of unused fields */ max--; } for (a = 0; a <= max; a++) { /* dump nonzero memory */ putc( (memory[a] >> 6) + '!', dumpfile ); putc( (memory[a] & 077) + '!', dumpfile ); d++; if (d >= 32) { putc( '\n', dumpfile ); d = 0; } } if (d != 0) { putc( '\n', dumpfile ); } /* take care of CORE first, never record that binding * * because recording it prevents renaming a core file */ corename[0] = '\0'; /* ugly dismount of core */ foralldevices( dumpdevice ); fputs( "end\n", dumpfile ); fclose( dumpfile ); } } xxxxxxxxxx cat > utility.h <<\xxxxxxxxxx /* File: utility.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 29, 2025 Language: C (UNIX) Purpose: interface to DEC PDP-8/e emulator utility routines */ /* Copy string s into a file name buffer f, checking safety */ void set_file_name( char *f, char *s ); /* command line argument processing */ void getargs( int argc, char *argv[] ); /* The following two routines read and write core image files using a * * simple variant of Charles Lasner's .IPL file format. */ void readcore(); void dumpcore(); 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: July 31, 2025 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). Alternately (?), the include file can be changed to and the code can be compiled with the bsd library */ #include #include #include #include #include #include #include "ttyaccess.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 */ /************************/ static struct termios oldstate; /* tty state prior to reset */ void ttyrestore(); /* forward declaration */ /* called to kill process, and as SIGTERM signal handler */ static void mykill(int ignored) { ttyrestore(); exit( EXIT_FAILURE ); } /* save tty state and convert to raw mode */ void ttyraw() { /* take over the interactive terminal */ { /* get old TTY mode for restoration on exit */ tcgetattr( keyboard, &oldstate ); } { /* put TTY in RAW mode; note: raw mode may be a bit drastic! */ struct termios newstate; tcgetattr( keyboard, &newstate ); /* line attributes */ newstate.c_lflag &= ~ISIG; /* don't enable signals */ newstate.c_lflag &= ~ICANON;/* don't do canonical input */ newstate.c_lflag &= ~ECHO; /* don't echo */ /* input attributes */ 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 */ #ifdef IUCLC newstate.c_iflag &= ~IUCLC; /* don't map upper to lower */ #endif newstate.c_iflag &= ~IXON; /* don't enable ^S/^Q on output */ newstate.c_iflag &= ~IXOFF; /* don't enable ^S/^Q on input */ #ifdef IUTF8 newstate.c_iflag &= ~IUTF8; /* don't support UTF */ #endif /* output attributes */ newstate.c_oflag &= ~OPOST; /* don't enable output processing */ #ifdef OLCUC newstate.c_oflag &= ~OLCUC; /* don't map lower to upper */ #endif #ifdef VMIN newstate.c_cc[VMIN] = 0 ; newstate.c_cc[VTIME] = 1 ; #endif /* note: on some UNIX systems, no amount of urging seems to make it insist on converting cr to nl */ tcsetattr( keyboard, TCSADRAIN, &newstate ); } { /* install handlers to catch attempts to kill process */ /* should probably catch other fatal signals too */ signal( SIGTERM, mykill ); } } /* return console to user */ void ttyrestore() { tcsetattr( keyboard, TCSADRAIN, &oldstate ); } /*********************/ /* user I/O routines */ /*********************/ /* put character to console */ void ttyputc( 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 * * used with KC8E to merge keypress events 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]; /* stuff a char into input stream from auxiliary source */ void ttystuff( char ch ) { int newtail = (stufftail + 1) % stufflen; if (newtail != stuffhead) { /* discards excess input */ stuffqueue[stufftail] = ch; stufftail = newtail; } } /* poll for a character from the console */ int ttypoll() { 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 ); fcntl( keyboard, F_SETFL, flag | O_NDELAY ); 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; } } /* blocking 7 bit read from console */ int ttygetc() { char buf; if (stuffhead != stufftail) { buf = stuffqueue[stuffhead]; stuffhead = (stuffhead + 1) % stufflen; } else { int count; do { /* blocking mode ought to prevent count == 0 */ if (mode != BLOCKING) { /* make blocking */ int flag; flag = fcntl( keyboard, F_GETFL, 0 ); fcntl( keyboard, F_SETFL, flag & ~O_NDELAY ); mode = BLOCKING; } count = read( keyboard, &buf, 1 ); } while (count <= 0); } breakcount = 0; return buf & 0177; } /* put string to console */ void ttyputs( const char *buf ) { int count; for (count = 0; buf[count] != '\0'; count++) {;}; count = write( display, buf, count ); fsync( display ); } /* get string from console */ void ttygets( 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 > ttyaccess.h <<\xxxxxxxxxx /* File: ttyaccess.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 30, 2025 Language: C (UNIX) Purpose: Interface to code to take over the tty and put it in raw mode */ /* The following trick puts extern on definitions if not in the main program */ #ifdef MAIN #define EXTERN #else #define EXTERN extern #endif /************************/ /* startup and shutdown */ /************************/ /* save tty state and convert to raw mode */ void ttyraw(); /* return console to user */ void ttyrestore(); /*********************/ /* user I/O routines */ /*********************/ /* set by user, callback when 5 consec ^C seen */ EXTERN void (* ttybreak) (); /* put character to console */ void ttyputc( char ch ); /* stuff a char into input stream from auxiliary source */ void ttystuff( char ch ); /* poll for a character from the console */ int ttypoll(); /* blocking 7 bit read from console */ int ttygetc(); /* put string to console */ void ttyputs( const char *buf ); /* get string from console */ void ttygets( char *buf, int len ); xxxxxxxxxx cat > realtime.c <<\xxxxxxxxxx /* File: realtime.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: July 26, 1995 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 */ /* initialize timer subsystem */ void init_timers() { head = niltimer; /* there is no pending timer initially */ countdown = 0x7FFFFFFF; /* so put off next firing a long time */ } /* schedule timer t after a delay of d ticks */ void schedule( 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; } } } /* find how much time remains on t */ long int query_timer( struct timer *t ) { if (t->delay < 0) { /* timer is not in queue */ return -1; } else { long int query = countdown; struct timer * i = head; while (i != t) { /* scan list for t */ i = i->next; query = query + i->delay; /* accumulate delays */ } return query; } } /* cause timer to fire at current time */ void fire_timer() { 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: Nov. 13, 1995 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 */ /* schedule timer t after a delay of d ticks with parameter p */ void schedule( struct timer * t, long int d, void (* a)(), int p ); /* value to pass as p if no parameter is needed */ #define NOPARAM 0 /* find how much time remains on t */ long int query_timer( struct timer *t ); /* cause timer to fire at current time */ void fire_timer(); EXTERN long 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 > devices.h <<\xxxxxxxxxx /* File: devices.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 5, 2025 Language: C (UNIX) Purpose: interface to tools for linking emulated devices to files */ /* on startup, the simulator for the device registers it */ void register_device( bool (* m)(), /* hook to mount file on device */ void (* d)(), /* hook to dismount mounted file */ int u, /* device unit */ char * n, /* device name; the caller must guarantee this is unique */ char * l, /* descriptive device name */ char * f /* file attached to device */ ); /* tool to to export information on each mounted device via a callback */ void foralldevices( void(* callback)( char *sname, char *lname, char *fname ) ); /* a clean exit from the emulator dismounts all devices */ void close_devices(); /* select an initial device */ void first_device(); /* select the next device */ void next_device(); /* find a device by its (short) name n, return FALSE if no such device */ bool get_device( char *n ); /* query (short) name of selected device */ char * device_name(); /* query longname of selected device */ char * device_longname(); /* query file mounted on selected device (returns pointer to mutable string) */ char * device_file(); /* try to mount file on selected device (file name must be non-empty) */ bool device_mount(); /* dismount any file mounted on selected device */ void device_dismount(); xxxxxxxxxx cat > devices.c <<\xxxxxxxxxx /* File: devices.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 4, 2025 Language: C (UNIX) Purpose: Utility for linking emulated devices to files */ #include #include #include #include #include "bus.h" /**********************************************/ /* Implementation of device control interface */ /**********************************************/ /* device records are organized into a circular list, one entry per device */ struct device_rec { bool (* mount)(); /* hook to mount file on device */ void (* dismount)(); /* hook to dismount mounted file */ int unit; /* param to mount and dismount, possibly a unit number */ char * name; /* short device name */ char * longname; /* descriptive device name */ char * file; /* buffer for name of file attached to device */ struct device_rec * next; /* next list element */ }; static struct device_rec * devices = NULL; /* on startup, the simulator for the device registers it */ void register_device( bool (* m)(), /* hook to mount file on device */ void (* d)(), /* hook to dismount mounted file */ int u, /* device unit */ char * n, /* device name; the caller must guarantee this is unique */ 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 circular list */ temp->next = temp; devices = temp; } else { /* link in to circular list */ temp->next = devices->next; devices->next = temp; } } /* tool to to export information on each mounted device via a callback */ void foralldevices( void(* callback)( char *sname, char *lname, char *fname) ){ struct device_rec *temp = devices; do { if ((temp->file)[0] != '\0') { /* device is mounted */ callback( temp->name, temp->longname, temp->file ); } temp = temp->next; } while (temp != devices); } /* a clean exit from the emulator dismounts all devices */ void close_devices() { struct device_rec *temp = devices; do { (* (temp->dismount))( temp->unit ); temp = temp->next; } while (temp != devices); } /* private handle used by tools for study and manipulation of devices */ static struct device_rec * selected_device; /* select an initial device */ void first_device() { selected_device = devices; } /* select the next device */ void next_device() { if (selected_device != NULL) { selected_device = selected_device->next; } } /* find a device by its (short) name n, return FALSE if no such device */ bool get_device( char *n ) { if (devices == NULL) return false; /* device list empty */ first_device(); do { if (strcmp( selected_device->name, n) == 0) return true; next_device(); } while (selected_device != devices); /* name not found */ selected_device = NULL; return false; } /* query (short) name of selected device */ char * device_name() { if (selected_device != NULL) { return selected_device->name; } else { return NULL; } } /* query longname of selected device */ char * device_longname() { if (selected_device != NULL) { return selected_device->longname; } else { return NULL; } } /* query file mounted on selected device (returns pointer to mutable string) */ char * device_file() { if (selected_device != NULL) { return selected_device->file; } else { return NULL; } } /* try to mount file on selected device */ bool device_mount() { /* returns true on successful mount */ if (selected_device == NULL) return false; return (*(selected_device->mount))( selected_device->unit, selected_device->file /* assumes this is non-empty filename */ ); } /* dismount any file mounted on selected device */ void device_dismount() { if (selected_device == NULL) return; (*(selected_device->dismount))( selected_device->unit ); } xxxxxxxxxx cat > hobble.h <<\xxxxxxxxxx /* File: hobble.h Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 1, 2025 Language: C (UNIX) Purpose: interface to code to make simulated time match real time. */ /* HOBBLE, in milliseconds, is defined by MAKEFILE, 1 second or less */ void hobblepower(); xxxxxxxxxx cat > hobble.c <<\xxxxxxxxxx /* File: hobble.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 3, 2025 Language: C (UNIX) Purpose: Slow down the simulation so that simulated time matches real time. Mechanism: Call hobblepower() to launch a simulation process that causes the simulator to sleep every HOBBLE simulated milliseconds, with the sleep delay calculated to pad each process cycle to HOBBLE real milliseconds. */ #include #include #include "realtime.h" /* HOBBLE, in milliseconds, is defined by MAKEFILE, less 1 second or less */ /*************************************** * Code missing from time.h * ***************************************/ static void timespec_diff( const struct timespec *a, const struct timespec *b, /* logically, result = a - b */ struct timespec *result /* result may same object as a or b */ ) { result->tv_sec = a->tv_sec - b->tv_sec; result->tv_nsec = a->tv_nsec - b->tv_nsec; if (result->tv_nsec < 0) { /* borrow from seconds field */ result->tv_sec -= 1; result->tv_nsec += 1000000000L; } return; } static void timespec_sum( const struct timespec *a, const struct timespec *b, /* logically, result = a + b */ struct timespec *result /* result may same object as a or b */ ) { result->tv_sec = a->tv_sec + b->tv_sec; result->tv_nsec = a->tv_nsec + b->tv_nsec; if (result->tv_nsec >= 1000000000L) { /* carry to seconds field */ result->tv_sec += 1; result->tv_nsec -= 1000000000L; } return; } /*************************************** * Mechanism * ***************************************/ /* simulated time interval between hobble events */ #define PERIOD (HOBBLE * millisecond) /* real time interval between hobble events, in nanoseconds */ #define NPERIOD (HOBBLE * 1000000l) static const struct timespec nperiod = { .tv_sec = 0, .tv_nsec = NPERIOD }; /* timer used to schedule hobble events */ static struct timer hobble_delay; /* record of the wall clock time we are trying to match */ static struct timespec target; static void hobble( int ignored ) { struct timespec now; /* wall clock time at which this event occurs */ struct timespec delay; /* eventually, delay to use */ clock_gettime( CLOCK_MONOTONIC, &now ); /* advance target time to what we want now to be */ timespec_sum( &target, &nperiod, &target ); /* figure out how much to delay to make now match new target */ timespec_diff( &target, &now, &delay ); if (delay.tv_sec >= 0) { /* simulation is faster than real PDP-8 */ if (delay.tv_sec > 0) { /* cap the delay at 1 second */ delay.tv_sec = 0; delay.tv_nsec = 999999999; } if (delay.tv_nsec > 0) { /* a delay is needed */ /* Note: a signal can wake nanosleep early */ while ((nanosleep( &delay, &delay ) != 0) && (errno == EINTR) ); } } else { /* simulation is slower than a real PDP-8 */ target = now; } /* continue hobble process */ schedule( &hobble_delay, PERIOD, hobble, NOPARAM ); } /*************************************** * Interface * ***************************************/ void hobblepower() { /* launch hobble process to keep time in sync with wall time */ clock_gettime( CLOCK_MONOTONIC, &target ); init_timer( hobble_delay ); schedule( &hobble_delay, PERIOD, hobble, NOPARAM ); } xxxxxxxxxx cat > coredump.c <<\xxxxxxxxxx /* File: coredump.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Date: Aug. 19, 2025 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 #include #include "bus.h" #include "utility.h" /* stub to fake stuff utility.c calls that we never use */ void ttyrestore() { return; } /* dump all of memory */ void dump() { 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-8) { 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 */ int main( int argc, char *argv[] ) { getargs(argc, argv); if (corename == NULL) { /* no file specified */ fprintf( stderr, "%s: no core file specified\n", argv[0] ); exit( EXIT_FAILURE ); } readcore(); dump(); } xxxxxxxxxx cat > coremake.c <<\xxxxxxxxxx /* File: coremake.c Author: Douglas Jones, Dept. of Comp. Sci., U. of Iowa, Iowa City, IA 52242. Credit: Vince Slingstad showed how to make it accept RIM and BIN format Date: Aug 25, 2025 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 #include #include #include "bus.h" #include "utility.h" #include "devices.h" /* stub to fake stuff utility.c calls that we never use */ void ttyrestore() { return; } /* stubs to fake stuff devices.c needs that we never use */ static void closecore( int u ) { return; } static bool opencore( int u, char *f ) { return true; } /* Gripe about bad load address, never returns */ static void gripe( char * progname, int ma ) { fprintf( stderr, "%s: tried to load memory[%05o] " "when memory size is %05o\n", progname, ma, MAXMEM ); exit( EXIT_FAILURE ); } /* RIM loader (logic reproduced as closely as possible from DEC documents) * * with tricks Vince Slyngstad showed me to make it also read BIN format. */ void rim( char *progname ) { int c; int cc = 010000; /* initial value suppresses a load step */ int ma = 0; for (;;) { for (;;) { /* skip leader and process certain BIN features */ if ((c = getchar()) == EOF) { return; /* exits both loops */ } if (!(c & 0200)) { /* high bit reset, end of leader */ break; /* exits inner loop */ } if ((c & 0300) == 0300) { /* set memory field */ /* here, we know it's BIN format */ if (!(cc & 010000)) { /* load step not suppressed */ if (ma >= MAXMEM) gripe( progname, ma ); memory[ ma ] = cc; } ma = (c & 070) << 9; } else { cc = 010000; /* suppress next load step */ } } /* neither leader nor trailer nor BIN field setting */ if (!(cc & 010000)) { /* if load step not suppressed */ if (ma >= MAXMEM) gripe( progname, ma ); memory[ ma ] = cc; ma = ma + 1; /* allows for BIN format */ /* here, if !(c & 0100), we know it's BIN format */ } c = (c << 6); if ((cc = getchar()) == EOF) { return; /* second exit from outer loop */ } c = c | cc; cc = c; /* suppres next load step if memory addr set */ if (c > 07777) { /* set memory addr, with no field change */ ma = (ma & 070000) + (c & 07777); } } } void dump_devices( FILE *f ) { return; } /* main program */ int main( int argc, char *argv[] ) { getargs(argc, argv); register_device( opencore, closecore, 0, "CORE", NULL, corename ); if (corename == NULL) { /* no file specified */ fprintf( stderr, "%s: no core file specified\n", argv[0] ); exit( EXIT_FAILURE ); } rim( argv[0] ); dumpcore(); exit( EXIT_SUCCESS ); } xxxxxxxxxx