Stephen Graham Pratt
Clare College






Emulating a DEC PDP-8/e


A Project for the Diploma in Computer Science 1995


Candidate:	Stephen Graham Pratt, Clare College

Title:	Emulating a DEC PDP-8/e

Examination:	Diploma in Computer Science 1995

Word Count (approx.):	10,000

Project Originator: 	Dr LC Paulson

Project Supervisor: 	Dr LC Paulson




Original Aim:

The aim of this project was to create a functionally correct emulator for the basic 
model DEC PDP-8/e computer (but with the full 32 kilowords of memory), with a 
graphical user interface to emulate the appearance of the real machine. As proof of the 
correctness of the implementation it was hoped that the emulator would run DEC's 
diagnostic test programs and the interpreter for FOCAL (a language supplied by 
DEC). It was also hoped that the emulator would run at a speed comparable with that 
of the original PDP-8/e.


Work Completed:

A working emulator with a graphical user interface was produced. It successfully ran 
DEC's diagnostic tests about 24 times slower than the original computer. The FOCAL 
interpreter also ran correctly: both responding to input and executing a simple 
program at a perfectly reasonable speed.


Special Difficulties:

The actual PDP-8/e hardware, for which the diagnostic programs and the FOCAL 
interpreter were designed, did not conform precisely to the description in DEC's 
handbook. The discrepancies were sufficient to cause these programs to fail until the 
faults were found and corrected.
The machine's interrupt system was intricate and sensitive to subtle errors in the 
implementation. These also caused failure until the faults were corrected.


Table of Contents


Chapter 1:  Introduction	1

Chapter 2:  Preparation	4
Summary	4
2.1  Understanding the PDP-8/e	4
2.2  Thread Structure	5
2.3  Information Structure	6
2.4  Graphical User Interface	7
2.5  Planning for Evaluation	8
2.6  Insurance Against Accidents	8

Chapter 3:  Implementation	9
Summary	9
3.1  Library	9
3.2  Main Processor Cycle	10
3.3  User Interface	11
3.4  Control Thread	13
3.5  Instructions	13
3.5.1  Memory Reference Instructions	13
3.5.2  OPR Instructions	14
3.5.3  IOT Instructions	15
3.6  Interrupts	16
3.7  Extended Memory	18
3.8  Teletype and Paper Tape Reader	18
3.9  Removing Diagnostic Messages	20

Chapter 4:  Evaluation	21
Summary	21
4.1  Verification	21
4.1.1  Library	22
4.1.2  Main Processor Cycle and User Interface	22
4.1.3  Memory Reference Instructions	23
4.1.4  OPR Instructions	23
4.1.5  Interrupt System	24
4.1.6  Extended Memory Instructions	26
4.1.7  Teletype and Paper Tape Devices	26
4.1.8  DEC's Diagnostic Tests	27
4.1.9  Running u8-388 and FOCAL	27
4.2  Performance Testing	28
4.2.1  Diagnostic Messages	28
4.2.2  Register Accesses	29
4.2.3  Memory Accesses	29
4.2.4  Interrupts	29
4.2.5  Separate Threads	30
4.2.6  Best Performance	30


Chapter 5:  Conclusions	31

Bibliography	32

Appendix 1:  DEC's Diagnostic Tests	33
Appendix 2:  Instruction Formats	34
A2.1  Memory Reference Instructions	34
A2.1.1  Indirect Addressing	34
A2.1.2  Page Specification	34
A2.2  IOT Instructions	35
A2.3  OPR Instructions	35
Appendix 3:  Initialisation File	37
Appendix 4:  Interrupts	38
Appendix 5:  User Interface Commands	39
Appendix 6:  Paper Tape Formats	40
A6.1  RIM Format	40
A6.2  BIN Format	41
Appendix 7:  Removing the Diagnostics	42

Project Proposal	44



Chapter 1:  Introduction


The PDP-8/e was a digital computer with a 4 kiloword  core memory which could be 
upgraded to 32 kilowords. The control panel of the computer had two rows of 
indicator lights. The top row displayed the current address. The information displayed 
by the second row depended on the setting of the display selector switch. There were 
also twelve switches making up the switch register which was used in conjunction with 
the 'Address Load' and 'Deposit' keys to load data into memory. A fuller description of 
the keys on the front panel is given in appendix 5. The other facilities for input and 
output on the basic machine were a teletype (keyboard and printer) and a paper tape 
reader and punch.

The PDP-8/e was the most popular of the PDP-8 family of computers, which was 
introduced in 1965 and 'has been described as the Model T of the computer industry, 
because it was the first computer to be mass-produced at a cost that just about anyone 
could afford.'  This popularity was also due to the many expansion options which were 
developed by DEC, including a magnetic disc system and an oscilloscope display, 
which, along with the software provided, helped to make the PDP-8 family one of the 
most versatile and widely used computers of its time. One of the software packages 
was the interpreted language FOCAL (FOrmula CALculator) which was supplied with 
the PDP-8 family by DEC. This was similar in style and power to contemporary 
versions of BASIC. It was 'used by students, engineers and scientists in solving 
virtually any mathematical problem, and much more.'  There was also an assembler 
called PAL-8 which converted listings of machine instructions to files in BIN format  
which were then loaded as described below.

DEC also provided diagnostic test programs which could be run on the machine to 
verify that it was functioning correctly. 

The usual method of reading a program into the basic PDP-8/e was longwinded. 
Firstly, the seventeen instruction RIM loader, which was capable of reading tapes 
punched in RIM format, was loaded using the keys on the front panel. This was then 
used to load the more sophisticated BIN loader which recognised the BIN format in 
which most PDP-8 tapes were punched. Finally, the BIN loader was used to read in 
the program required.

The PDP-8/e could either be run in continuous mode or single step mode. In 
continuous mode, which was entered by pressing the 'Continue' key, instructions were 
executed in sequence until either a halt (HLT) instruction was encountered or the 'Halt' 
key was depressed on the front panel. In single step mode, instructions were executed 
one at a time in response to depression of the 'Single Step' key.



The PDP-8/e was a single accumulator (AC) machine, with additional temporary 
storage in the multiplier quotient register (MQ). There was also a flags register which 
included a one bit link flag. This performed the same function as the carry on a modern 
machine.

All machine instructions were one 12-bit word in length. The top three bits of the 
instruction specified the operation code (opcode): hence there were eight basic 
instruction types. A brief summary is given below. 

Opcodes 0-5 were memory reference instructions with the low nine bits specifying the 
memory location Y. These instructions were:

AND Y	logical AND between AC and Y, storing result in AC
TAD Y	add Y to AC using two's complement arithmetic
ISZ Y	increment Y and skip the next instruction if Y becomes zero
DCA Y	deposit AC at Y and clear AC
JMS Y	jump to subroutine at Y
JMP Y	jump to Y

The PDP-8/e had no hardware stack. The JMS instruction stored the address to which 
to return (i.e. the current value of the program counter) at Y and the actual code of 
the subroutine starts at Y+1. This method makes re-entrant subroutines difficult to 
write.

Opcode 6 indicated an input/output transfer (IOT) instruction: the rest of the bits 
specify the device to be addressed and a device-specific command.

Opcode 7 indicated an operate (OPR) instruction. These manipulated the AC, MQ and 
flag register contents (e.g. CMA (CoMplement Accumulator) and RTL (Rotate Two 
Left)) and provided conditional execution via instructions which will skip the next 
instruction (which may be a JMP instruction) if tests on the AC and flag registers 
succeed.

A program interrupt facility was also available: this needed to be implemented in the 
emulator for DEC's diagnostic tests and FOCAL to run correctly.

A few PDP-8 computers are still in use and occasional appeals for hardware, software 
and technical assistance for the family are posted on the Usenet news group 
alt.sys.pdp8.



The most significant previous work in the area which I found was a fully functional 
emulator written in C by Douglas Jones of the University of Iowa. This was not used 
as a major resource, as it was decided to implement the new emulator in Modula-3 
using separate threads of execution. It was, however, used as a reference against 
which to compare the results of tests of the new emulator  and when resolving a few 
of the more tricky problems encountered in the implementation. 

Note: most PDP-8 documentation uses octal as its primary base, and that convention 
is followed in this report.


Chapter 2:  Preparation


Summary

Jones' emulator was compiled and tested. It was then decided exactly what should be 
implemented in the new emulator and the data structures required to represent its 
internal state were described. The separate asynchronous threads of execution required 
were defined. Modula-3 was chosen as the language in which to implement the 
emulator as it provides a well supported facility for such threads. The user interface 
design tool formsedit was investigated. Various features were specified to aid 
evaluation of the system. A system of regular backups was defined.



2.1  Understanding the PDP-8/e

Several files were downloaded from the World Wide Web.  These were: the source for 
Jones' emulator, a cross-assembler for the PAL-8 assembly language used by the 
PDP-8 family, a program in BIN format called u8-388 (which, when given a year, 
produces a calendar for it), and a copy of the FOCAL interpreter.

A version of Jones' emulator with a graphical user interface similar to the front panel 
of the real machine was compiled.  DEC's diagnostic test programs  were run on this 
emulator to demonstrate that it is implemented correctly: it ran the second test 
program about three times slower than the original machine. The FOCAL interpreter 
and the calendar program were also successfully run on this emulator.

Running programs on the emulator gave an opportunity to get a feel for how the 
PDP-8/e worked and how it was used. Having done this it was decided to implement 
the basic machine (but with 32 kilowords of memory). It was decided that there 
probably would not be time to implement any optional extras, so these were not 
considered further at this stage.

DEC's books on the PDP-8/e itself  and on how to program it  were read. These were 
the main references used throughout the project. Both the source code of Jones' 
emulator and the Usenet news group alt.sys.pdp8 were used to clear up points where 
the description in these books was insufficiently precise.




2.2  Thread Structure

The inherent asynchrony of a computer system suggests the use of several separate 
threads of execution to implement an emulator. The threads used were:

1.	control thread: responsible for initialising the emulator, starting all the other 
threads, and tidying up at the end.

2.	main processor cycle: reads instructions from memory and implements them, also 
handles interrupt requests.

3.	user interface: accepts commands from the user, which may be issued while the 
processor is running.

4.	character reader: attempts to read characters from input files when requested by 
the main processor cycle; raises an interrupt (if appropriate) when a character is 
ready.

The separate threads enable the processor thread to run more quickly, without 
constantly needing to check for input from the user interface and from files. Modula-3 
was chosen as the language to use as this provides good support for separate 
execution threads. It was therefore necessary to build the emulator from scratch 
because no other emulators written in Modula-3 have been found.

The use of separate threads does slow down the emulator  but they made program 
design easier.





2.3  Information Structure

The emulator is represented as a set of objects as shown in figure 1.



 



The 'Core Status' object records the current status of the emulator. The registers used 
are: accumulator, program counter, link register, instruction register , multiplier 
quotient, memory buffer , switch register , SW line register , instruction field, data 
field, instruction field buffer, instruction field save register and data field save register. 
The last two in this list are used by the interrupt system to save the current values of 
the field registers.



The inter-thread signals are:

*	a signal from the processor informing the user interface that a cycle has been 
completed
*	a signal from the user interface requesting the processor to execute one or more 
instructions
*	a signal which can be raised by any thread to request that the control thread 
terminate the program

The execution status consists of two flags indicating:

*	whether the emulator is currently executing an instruction
*	whether it is in continuous mode

The system bus is represented as an object containing a separate object to represent 
each device attached to the bus. The bus object has a command method which is 
invoked by the processor cycle to implement all IOT instructions. This in turn invokes 
the command method of the appropriate device to implement the instruction. Each 
device is responsible for implementing a specific group of IOT instructions. 



2.4  Graphical User Interface

Modula-3 provides support for graphical displays on X terminals via a system called 
FormsVBT. Graphical displays are implemented as an object hierarchy. To simplify the 
task of designing a display, the formsedit tool is provided. This uses a Lisp-like 
language to specify the display, and allows the designer to try out specifications as 
they are typed in. The FormsVBT system provides procedures to create displays from 
files containing such specifications.
The use of macros to describe repeated non-standard objects (e.g. indicator lights and 
push switches) in this language was successfully investigated.
The attachment of callback procedures to objects in the display in order to implement 
the functions of keys on the front panel was also investigated. It was found that these 
procedures are required to have a standard signature.  This makes it impossible to 
pass an object representing the status of the emulator directly into the procedure. 
Therefore, a pointer to this object must be set up in the module containing the 
procedure when the program is initialised.





2.5  Planning for Evaluation

The Unix Revision Control System (RCS) was used to store each revision of each 
source code file, so that results of any test could be accurately reproduced.

The profiling system which is part of SRC Modula-3 was investigated. This records 
the number of times each line and procedure in the source code is executed. This 
shows which the most heavily used sections of code are, and hence the areas which 
need optimisation. Unfortunately there is an error in the SunOS5 issue of Modula-3 
which prevents profiling information from being produced. It was therefore necessary 
to obtain an account on the Computing Laboratory Unix system to give access to 
SunOS4 machines (where the bug is not present) on which profiling could be 
performed.

The following facilities were included in the emulator to aid evaluation:

1.	The ability to produce diagnostic messages while running. These messages were 
used to pinpoint errors and to verify parts of the program. The messages were 
divided into different types and the types to be produced during a given run may be 
specified in the initialisation file. 

2.	The ability to load and save core memory images. Programs to inspect these 
images and to produce them from files in BIN format  were also written. BIN 
format is the form in which most PDP-8 paper tapes were produced, and in which 
programs are available from the World Wide Web. The ability to convert from 
BIN format and then load directly into the emulator makes loading programs much 
easier than the method used on the original machine, which is described in 
chapter 1.

3.	The ability to start the emulator automatically at a predefined address when the 
program is run, and to stop it after a specified number of instructions. This allowed 
tests to be run over long periods of time without a user interface. It also allowed 
tests to be run at night when the system was less heavily loaded.



2.6  Insurance Against Accidents

To prevent accidents or system crashes from causing a disastrous loss of data, all 
source code, documentation and test result files were combined together twice weekly 
using the Unix tools 'tar' and 'gzip' and stored on the Pelican archive system. RCS is 
also useful in this respect: if a source file is accidentally deleted, only the changes made 
since the file was last checked into RCS are lost.


Chapter 3:  Implementation


Summary

The emulator was implemented in sections as described in this chapter. After each 
section had been written it was tested as described in chapter 4. The main stages of 
implementation were as follows:

*	A library of foundation modules was built
*	The main processor cycle (with dummy procedures to implement instructions) and 
the keyboard user interface were implemented. These were implemented 
simultaneously so that they could be used to test each other.
*	Instructions (and all the associated hardware emulation) were then added along 
with the interrupt system.
*	The whole emulator was tested for correctness.
*	The graphical user interface was added.

As a result of the evaluation described in chapter 4, changes were then made to the 
implementation to improve the performance of the emulator. This included a program 
(written using lex) to reversibly comment out all calls to the procedure generating 
diagnostic messages. Other improvements are described in chapter 4.



3.1  Library

The foundations on which to build the emulator were placed in a library. These include 
the objects in the 'Core Status' section of figure 1 as well as modules to:

*	return values of parameters specified in an initialisation file 
*	produce diagnostic messages

There are also several points at which the emulator needs to be able to manipulate 
individual bits within a binary number. Procedures to convert between binary numbers 
and equivalent arrays of BOOLEANs were therefore included in the library so that 
individual bits could be accessed easily when required.

The registers module supplies an object type representing a single register. The size of 
a register is specified when the object is instantiated. Read, write and increment 
methods are provided. These allow the register value to be accessible only through 
object methods, which can then ensure that only valid values can be written to a 
register. The implementation of registers was changed when it was found that the 
emulator is slowed down significantly by the method described here. 


Reading from, writing to, and incrementing memory locations were originally 
implemented as methods of the memory object so that the interface would be 
transparent to the way in which the contents of memory were actually stored. This was 
later changed  in an attempt to speed up all memory accesses, and reads from memory 
in particular. The memory module includes the ability to load and save images of the 
core memory of the emulator as specified in section 2.5. This uses the built in 
Modula-3 interface called Pickle which allows any reference type (including any 
object) to be stored in a Unix file.

To ensure correct signalling between threads an object type was created which stores a 
signal if there is no thread waiting for it. (Modula-3 condition variables discard any 
signal for which no thread is waiting.) The next thread to wait on a signal via that 
object will detect the signal (and clear it) and will not block. An example of where this 
is necessary is the interlock between the processor thread and the user interface when 
running in single step mode. The user interface thread sends a signal requesting the 
processor to execute a cycle, and then waits for a signal from the processor that the 
cycle has been completed before proceeding. If either signal is issued before the target 
thread is ready for it, and if the signal is then lost, as would occur with standard 
condition variables, then deadlock will occur as each thread is waiting for a signal from 
the other.



3.2  Main Processor Cycle

The main processor cycle implemented by the processor thread is a loop which runs 
until either stopped by the user via a 'Halt' command from the user interface or 
because the emulator has executed the number of instructions required. 

The cycle proceeds as follows:

*	If interrupts are enabled the thread will check whether any are pending. If there 
are, it will invoke the interrupt handler as described in appendix 4.
*	The instruction pointed to by the program counter is loaded and, if it is a memory 
reference instruction, the operand is decoded to determine the address to which it 
refers. 
*	The program counter is incremented. 
*	The instruction is executed.
*	The processor signals to the user interface that it has finished a cycle.
*	If appropriate (i.e. if in single step mode or if halted  while executing this 
instruction) the processor then waits for a signal from the user interface before 
executing the next instruction.


3.3  User Interface

The information structure for both user interfaces is as shown in figure 2.


 



The user interface must have access to the 'Core Status' object to be able to display 
parts of its contents, and to deposit values into core memory. Access to the 'System 
Bus' object is required because the user interface is responsible for attaching files to 
input and output devices by invoking connect and disconnect methods of these 
devices which are contained within the bus object.

The keyboard user interface commands  were designed with the following 
considerations in mind:

*	close emulation of the front panel keys
*	speed of use
*	ease of implementation

It was not required that the user interface commands be particularly clear or intuitive 
as they were only intended to be used while developing the system.

The procedures to implement the commands were, where possible, written so that the 
same procedures could be used by the graphical user interface. This meant that their 
signatures had to conform to the signature required of a callback procedure by 
FormsVBT. 

Commands given to the user interface are searched for in an array containing each 
command paired with a pointer to the procedure to be invoked to implement it. The 
commands requiring further parameters are required to read these from the input for 
themselves.



The user interface is responsible for displaying status information comprising:

*	the current address
*	whether the emulator is running
*	other information depending on the display mode. 

The keyboard user interface prints this information after each command is completed. 
The graphical user interface updates the indicator lights with this information after 
each depression of a key on the front panel and whenever the user interface thread is 
scheduled while the emulator is running in continuous mode. This ensures that the 
display is kept reasonably up to date without sacrificing speed by insisting on an 
update during each processor cycle.

The status update is achieved by the procedure pointed to in the user interface 
object.  This pointer is set up on initialisation depending upon which user interface (if 
any) is in use. The shared user interface code is then able to use this pointer to invoke 
the appropriate update procedure.

The picture between pages 12 and 13 of this report is the final graphical user interface. 
The image was captured when FOCAL had just been initialised and was ready to 
accept a program. The top section is a slightly adjusted version of the front panel of 
the original machine. The rows of squares are the indicator lights. The diamonds are a 
set of radio buttons representing the display selector switch: the area alongside this 
gives details of what the lights mean for each setting of the selector. Below this is the 
bank of switches whose functions are described in appendix 5. These are implemented 
as a type of button so that clicking the mouse on them has the same effect as pressing 
the switch on the original machine.

The next section of the display (below the row of switches) contains fields in which to 
specify files to be connected to the paper tape device along with buttons to perform 
the actions of connection and disconnection. Below this there is a box in which certain 
messages relating to attempts to connect these files are displayed. At the bottom of the 
display is a scrollable window which displays the teletype output. Characters typed 
when the cursor is over this region are treated as teletype input.





3.4  Control Thread

The control thread initialises the data structures and threads which make up the 
emulator. Much of the information to do this is obtained from the initialisation file.  
Defaults are used for any parameter not given in the initialisation file, or if no 
initialisation file is specified.

The most complex task is setting up the user interface. This is because several settings 
depend on whether the graphical or keyboard user interface (or none) is to be used. A 
pointer to the appropriate status update procedure  must be set up. If the graphical 
user interface is required, connections must be made between the object on the display 
emulating the teletype and the device object which implements the teletype instructions 
(and hence needs access to a source and a sink for teletype characters).

The initialisation file may also specify Unix files to be connected to the paper tape 
device (and to the teletype device if the graphical user interface is not being used): 
these connections are made by the control thread.

Having initialised everything, the control thread starts the other threads running and 
waits for one of them to signal that the program should terminate. (This may be 
because the user has requested termination, because the specified maximum number of 
instructions  has been executed, or due to an error condition.) The control thread 
then signals to all the other threads that they should finish and waits for them to do so. 
(It is therefore necessary for all threads to check for this signal frequently and to finish 
quickly when the signal is detected. The signal is implemented using Modula-3's 
system of alerts.)

Finally the current state of core memory may be saved to a file before the program 
exits.



3.5  Instructions 

3.5.1  Memory Reference Instructions

The memory reference instructions  (opcodes 0-5) were implemented as an array of 
procedures indexed by opcode. An alteration which might speed up the emulator 
would be to incorporate the code of these procedures as a CASE statement in the 
main processor loop to remove the overhead of a procedure call. The problem which 
was encountered when implementing these instructions is described in section 4.1.3.


3.5.2  OPR Instructions

Two alternative methods were used to implement OPR instructions. The first was 
simpler to implement and compiled more quickly. The implementation of the second 
method is more complicated but is derived from the code for the first. It took 
significantly longer to compile as the source code module which implements the 
instructions is over 8,000 lines long, but was expected to execute more quickly for 
reason given below.

The first method identifies which group the instruction is in and then invokes the 
procedure which executes instructions in that group. This procedure examines the rest 
of the bits in the instruction and executes code for each of the (possibly multiple) 
actions specified by these bits.  (There is a prescribed order  in which the functions 
in a group are to be executed and the bits are examined and the functions executed in 
this order.)

The second method is implemented as an array containing a pointer to a separate 
procedure for each possible bit combination in any of the three groups. The operand of 
the OPR instruction is then used as a single nine bit number to index into this array. 
These procedures were generated by a program which decodes each possible 
instruction and writes the required commands to a Modula-3 source file. This program 
was developed from the code used for the first method above by altering it to write 
commands to a file rather than executing them directly. The second method was 
expected to execute faster as the decoding of the instruction has already been 
performed at compile time.

Clarity of implementation, rather than efficiency, was aimed for. Improvements would 
have been possible within the implementation of individual functions but these would 
only have had a marginal improvement on overall speed. The exception to this would 
be programs making particularly heavy use of the individual functions whose 
implementation was improved.

To make the choice of method independent from the rest of the program, both 
methods were implemented as modules exporting the OPR interface which contains a 
single procedure. The sole purpose of this procedure is to pass information from the 
processor cycle into the code implementing the instructions. The method required can 
then be chosen by specifying a switch at compile time. This switch is tested in the 
m3makefile where it is used to decide which module (and hence which method) to 
link into the program. This technique produces an overhead of at least one extra 
procedure call per OPR instruction.

A possible way to speed up execution (of the first method) would be to remove this 
intermediate interface and to decode the instruction to discover its group in the main 
cycle. The appropriate procedure to implement that instruction group could then be 
called directly from the processor cycle. Switching to use the second method of 
implementing OPR instructions would then require alteration of the source code of the 
processor cycle to index into the array of precompiled procedures rather than calling 
the appropriate instruction group's procedure.

The instructions which were particularly awkward to implement were the rotation 
instructions. These treat the link and the AC together as a single thirteen bit register. 
The instructions specify a rotation which may be by either one or two bits and either to 
the left or to the right. Bits which 'fall off' one end are replaced at the other. Each of 
these instructions involves a significant amount of manipulation of individual bits to 
achieve the correct result.



3.5.3  IOT Instructions

IOT instructions are all implemented as commands to devices on the system bus.  The 
command method of the bus object is invoked, and the operand of the instruction and a 
pointer to the core status object are passed to it as parameters. Access to the 'Core 
Status' object is needed as its contents may be altered by the code implementing the 
instruction. The appropriate device is chosen in accordance with the device selector 
portion of the instruction. 

The command portion of the instruction and the core status pointer are then passed on 
to the command method of the device object which in turn implements the instruction. 
Some of the device objects actually implement commands for more than one value of 
the device selection bits, so these bits are also passed to the device object.

There is one exception to this: the CAF (Clear All Flags) instruction. This clears the AC 
and link registers, and then resets interrupts and all the devices attached to the bus. To 
perform this last task it is necessary to invoke each device object's reset method. If 
the CAF command were passed on to a device object according to its device selector 
bits, it would then be awkward to arrange for it to have access to each device on the 
bus so that it could reset them. Instead, CAF is handled in the command method of the 
bus itself, where the bus object is accessible, and through it all the device objects.





3.6  Interrupts

For details of the PDP-8/e interrupt system, see appendix 4. It is implemented in two 
sections: the object containing the current state of interrupts and the device 
implementing the interrupt related instructions.

Several different unsuccessful attempts were made at an object to correctly implement 
the current state of the interrupt system. Finally, an object with the following methods 
was produced:

1.	determine whether interrupts are enabled
2.	determine whether interrupts have been enabled and the one cycle delay has 
elapsed. 
3.	determine whether any interrupt requests are pending
4.	inform the object that another processor cycle has completed, so that it can keep 
track of the one cycle delay
5.	inform the object that interrupts have been enabled or disabled
6.	inhibit and uninhibit interrupts
7.	raise and cancel interrupt requests for individual devices
8.	reset the interrupt system (used when CAF is issued)

Each device may have at most one interrupt pending at any one time. So that the 
system can keep track of which devices do have interrupts pending, each device has a 
unique identifier (allocated at initialisation) by which it identifies itself to the interrupt 
status object when raising and cancelling interrupts.

The use of execution profiling  shows that methods 2 and 4 above are called during 
each processor cycle. To speed up the execution of these methods, the structure of the 
interrupt status object was changed so that a minimum number of variable 
manipulations is performed in each cycle. The fields required were also made directly 
accessible by the processor cycle to remove the overhead of making a procedure call 
to invoke an object method. The final implementation was:

1.	a counter which is incremented at the end of each processor cycle
2.	a cycle number indicating whether interrupts are enabled. If the counter is not less 
than this number then interrupts are active.
3.	a BOOLEAN flag to indicate whether interrupts are inhibited
4.	a set of flags indicating which devices' interrupts have been requested and not 
cancelled
5.	a counter giving the total number of interrupts currently pending. This allows the 
processor to quickly check whether any requests are pending.

To enable interrupts, the cycle number (2) is set to two more than the current value of 
the counter (1). The counter is incremented at the end of each cycle and interrupts are 
taken at the start of a cycle, so an interrupt (if there is one pending) will first be taken 
after a delay of one cycle, as required. 

To disable interrupts the cycle number is set to the largest representable number, 
which it is assumed that the counter will always be less than (but see below).

A side effect of this method is that the emulator is limited to executing a certain 
maximum number of instructions per run. This maximum is the largest number 
representable in a variable of type CARDINAL. The version of Modula-3 being used here 
uses 32 bit integers. As the type CARDINAL is defined as [0..LAST(INTEGER)], the 
largest CARDINAL is the same as the largest INTEGER: that is 231 - 1, which is about 
2 x 109. An average instruction on the PDP-8/e took 2.6 *s, so this represents about 
ninety minutes' computation.  If the count does reach this number then interrupts may 
be incorrectly enabled for one cycle, but then the emulator will fail, as the program will 
be terminated by the Modula-3 run-time system when an attempt is made to increment 
the counter beyond its upper limit.

Difficulties were encountered with the GTF, RTF and SKON instructions. This became 
apparent when DEC's diagnostic test programs failed.

SKON tests whether interrupts are enabled. If they are, it disables them and skips the 
next instruction. The problem with SKON was that it should skip the next instruction if 
interrupts are enabled irrespective of whether the one cycle delay  has elapsed: i.e. the 
sequence ION  SKON should cause the skip to be taken. I had originally assumed that 
this sequence should not cause the skip.

GTF and RTF are 'get flags' and 'restore flags' respectively. These perform transfers 
between the flags register and the accumulator. The actual PDP-8/e implements the 
GTF and RTF instructions in a different way to that described in the handbook.  The 
differences are that the actual values of some bits are ignored in the transfers and 
assumed to be either always set or always clear. These small discrepancies were 
sufficient to cause the diagnostic tests to fail. The problems were corrected  and the 
diagnostic programs then ran correctly.





3.7  Extended Memory

IOT instructions are provided to change the current instruction and data fields, and to 
read the current state of the field registers. The IF register is not changed immediately 
when the CIF (Change Instruction Field) instruction is issued. Instead the instruction 
buffer register (IB) is set to the new instruction field and interrupts are inhibited.  
When the next JMP or JMS instruction is encountered, interrupts are uninhibited and IF 
is set to the contents of IB. This allows transfers of execution between fields to be 
programmed easily by the sequence:

CIF field
JMP address

The field registers are saved when an interrupt is taken. There is an extended memory 
instruction to restore these values into the IB and DF registers which is used when 
returning from an interrupt.

The most complex part of implementing these instructions was ensuring that interrupts 
are correctly inhibited when a new value is placed in the IB register. It was also 
necessary to ensure that the JMP and JMS instructions correctly detect and deal with 
the inhibition, by loading the contents of IB into IF and uninhibiting interrupts.



3.8  Teletype and Paper Tape Reader

These were probably the most difficult parts of the emulator to implement as they 
require interaction between separate threads and the raising of interrupt requests. The 
implementations of the two devices have a lot in common and so are implemented 
using the object structure shown in figure 3.

The implementation of a character writer is straightforward. It is assumed that an 
output file is always ready to accept a character, so all that is needed is to write the 
character to the file. (It would be possible for a disc error to occur to prevent the write 
from taking place: in this case the writer will stall and never become ready again.)

The character reader object is actually a closure for a separate thread. (There is one 
thread each for the tape and teletype objects.) This waits until requested to fetch a 
character: it then blocks until a character is ready, and returns it in a one character 
buffer. If interrupts for the device are enabled, an interrupt is requested. The extra 
thread is necessary because characters can be typed at the teletype at any time, and an 
interrupt should be raised as soon as a new character is ready.

Commands are provided in the keyboard user interface to connect files to these 
devices. These invoke the connect and disconnect methods of the device objects as 
appropriate. When using the graphical user interface with integrated teletype, the 
teletype is connected on initialisation to file-like objects which act as channels between 
the teletype window on the interface and the teletype device object. Fields are 
provided on the graphical user interface in which to specify files for the paper tape 
device, as described in section 3.3.


 



When FOCAL was first run, standard input and standard output were used for the 
teletype. Unix buffers input from the console until a carriage return is entered, so that 
input is presented to a process a line at a time. This caused characters to be presented 
to FOCAL faster than its interrupt handler could deal with them. (The original I/O 
hardware was slow , so FOCAL had plenty of processing time available between 
characters.) The initial solution to this problem was to force the character reader 
thread to wait for a fixed length of time  before attempting to read the next character. 
This allowed time for FOCAL to process each character before being presented with 
the next. When the teletype was incorporated into the graphical user interface this 
problem was less acute as the teletype window presents characters to the reader thread 
individually as they are typed. Combined with the improvements in speed described in 
sections 4.2.1 - 4.2.4, this prevented FOCAL from crashing.





3.9  Removing Diagnostic Messages

It was found  that the procedure which is called most frequently is the one which 
optionally produces diagnostic messages. (The procedure was called Diag.Message.) 
Even when no messages are actually produced there is a procedure call overhead, as 
the decision whether to produce a message is made within the procedure. This 
overhead slows the emulator by a factor of at least 10.

A program was therefore written to reversibly comment out these calls from the 
source code, along with a complementary program to reinstate them. This ensures that 
the diagnostics are still available, at the expense of a recompilation.

The programs were written using the Unix tool lex. The token recognition part of the 
program to comment out calls is given in appendix 7; the program to reinstate the calls 
is almost identical but with the reverse function. When commenting out calls, the 
string 'Diag.Message' is replaced with '(*Diag.Message' and the next occurrence of 
');' is replaced with ');*)'.

The main function of the program (which is omitted from the appendix) reads a list of 
file names from a supplied input file, and executes the yylex function  on each one to 
perform the replacement.


Chapter 4:  Evaluation


Summary

The project was evaluated in two stages.

Each module of the program was verified as it was developed to ensure that it 
functioned correctly. This was done using separate Modula-3 test harnesses for the 
library modules, and by running short PAL-8 assembler programs on the emulator for 
most of the rest of the modules. The whole emulator was then verified by running 
DEC's diagnostic tests.

When all the functions required had been implemented, the performance of the 
emulator was assessed. This was measured in terms of speed of execution, using 
DEC's second diagnostic test program as a benchmark. Execution profiling was used 
to find the sections of the program which were executed most frequently. The code in 
these sections was then optimised for speed of execution. A reasonable speed was 
achieved: this was sufficient, for example, to run FOCAL interactively without having 
to wait too long for a response.



4.1  Verification

Each module was tested when it was first developed. When significant changes were 
made during later development the tests were repeated to ensure that the module still 
functioned as required.

Most of the verification tests involved running the emulator in the state of 
development which it was in at the time. Status and diagnostic outputs were examined 
to determine the results of the tests.

Many of the tests were in the form of short programs written in PAL-8 assembler, 
which were run by the emulator. The version of the PAL-8 assembler which was 
obtained from the World Wide Web did not recognise all the mnemonics required to 
write test programs. (For example, it did not recognise the GTF and RTF instructions.) 
Extra entries were added to the assembler's symbol table to correct this. The programs 
were assembled into BIN format using the cross-assembler, and then run on Jones' 
emulator to ensure that assumptions made about their results were correct. They were 
then converted from BIN format into memory images  to be loaded directly into my 
emulator. The results from running the programs were compared with those obtained 
from Jones' emulator.

Several references are made below to 'DEC's diagnostic tests running correctly.' 
Where this is in respect of corrections to a particular instruction this means that the 
tests no longer failed in the vicinity of that instruction.



4.1.1  Library

Each library module was verified by test harness programs written in Modula-3. The 
memory module tests ensured that a saved memory image was an accurate 
representation of the state of memory. (This was tested by saving an image and 
reloading it, and checking that the reloaded contents were the same as those saved.) 
The registers module was tested to ensure that only valid values could be written into 
a register.



4.1.2  Main Processor Cycle and User Interface

The processor cycle and keyboard user interface were tested simultaneously using 
tests which were sequences of user interface commands. Core memory was loaded 
with appropriate values using the 'Address Load' and 'Deposit' commands.  These 
values were then used to test the rest of the commands. Each command to be tested 
was issued, and the resultant state of the emulator was examined to verify that the 
command had caused the correct action to be performed. Invalid commands were 
correctly rejected by the emulator. The graphical user interface was similarly tested by 
checking that clicking the mouse on each key had the desired effect.

The emulator was presented with memory reference instructions representing all 
possible addressing modes  (including autoindexing) to check that the address was 
correctly calculated from the operand of an instruction.

The response of the emulator to a simulated interrupt request was also checked. If 
interrupts are enabled, it correctly stores the program counter and transfers control to 
location 0001 in field 0. The other actions to be taken on processing an interrupt 
request  were checked when interrupts were fully implemented. 





4.1.3  Memory Reference Instructions

Each of these was tested by writing a simple PAL-8 program to execute the 
instruction. For AND, ISZ and DCA this was simply a case of executing the instruction 
and examining the state of the accumulator and memory before and afterwards.

With TAD it was also necessary to inspect the state of the link register. Initially it was 
found that the program did not handle the this correctly. The implementation was 
corrected so that the link register is inverted if the sum of AC and memory exceeds 
7777 (octal). In other words, link and AC together are treated as a thirteen bit register. 
Overflow from this larger register is correctly ignored.

The JMP and JMS instructions were tested using a program containing a simple 
subroutine. This tests that the writing of the return address and the transfer of control 
are correct, but does not test the treatment of interrupts or the changing of the IF 
register. These were tested as part of the extended memory system tests. 



4.1.4  OPR Instructions

Identical tests were used for both interpreted and compiled versions of the OPR 
instructions. The tests described are not intended to be exhaustive as it would be 
prohibitively time-consuming to check that all 512 possible microcoded combinations 
of functions are correct. Instead, each individual function was tested and the DEC 
diagnostic tests were relied upon to highlight any errors arising from combining 
functions.

The test for group 1 instructions was simply a program which was executed in single 
step mode. The AC and link registers were examined after each instruction to verify 
that the results were correct. The rotate instructions  were found to be incorrect 
because the direction of rotation was the opposite to that required. This was easily 
fixed.

An error not detected until DEC's diagnostic tests were run was that IAC (Increment 
Accumulator) should carry into the link register if the AC is incremented from 
7777 (octal) to 0000.  No indication of this is given in DEC's specification,  but when 
the carry had been implemented, the diagnostic tests ran correctly.

The testing of group 2 instructions fell into two parts. The instructions which do not 
cause conditional skips were tested in the same way as the group 1 instructions: no 
problems were found here.

To test the conditional instructions, a program of the following form was used. The 
program was divided into several groups of instructions, each group being of the 
following format:

CLA	clear accumulator
SNA	skip if AC is non-zero
SZA	skip if AC is zero
HLT	halt

This tests that SNA will not skip, and that SZA will skip, if the accumulator is zero. To 
complete the tests of these instructions, their action under the opposite condition must 
be tested, so the following group is also needed: 

IAC	increment accumulator (so it is no longer zero)
SZA
SNA
HLT

Groups like this were placed in sequence in a program to test each of the conditional 
skip instructions in group 2. If any of the skip instructions had been incorrectly coded, 
then the program would have halted at the end of the appropriate group. This did not 
happen, and the program ran to the end, demonstrating that the group 2 instructions 
were correctly implemented.

Testing group 3 instructions was (like group 1) performed by stepping through a 
simple program and examining the contents of AC and MQ after each step. (On the 
basic PDP-8/e group 3 instructions simply provide transfers between MQ and AC.)



4.1.5  Interrupt System

To test the object storing the current state of interrupts a Modula-3 test harness was 
written which simulates conditions to ensure that the object correctly senses requests 
to enable, disable, and inhibit interrupts, and correctly reports on the current state. 
Particular attention was paid to ensuring that the one cycle delay between enabling 
interrupts and allowing them to be taken was correctly implemented. The raising and 
cancellation of interrupts and reporting on the state of each device's interrupt flag was 
also checked.



The interrupt system instructions were tested by the following program: 

	SRQ	skip next instruction if interrupts are pending - there are none at this point
	SKIP	unconditional skip - causes execution to proceed to IOF
*	HLT	halt - this occurs if the first instruction skipped in error
	IOF	disable interrupts
	SKON	if interrupts are enabled, skip next instruction and disable interrupts
	SKIP	unconditional skip - in normal circumstances execution will proceed to ION
*	HLT	this should be skipped unless SKON incorrectly caused a skip
	ION	enable interrupts after the next instruction
	NOP	no operation
	SKON	
*	HLT	
	TFL	teletype instruction - side effect used here is to raise an interrupt.           
NB interrupts are now disabled (by the SKON instruction) so the interrupt 
handler will not be invoked.
	GTF	read system flags to check that the interrupt has been raised (by manually 
inspecting the contents of AC)
	SRQ	skip if interrupt pending
*	HLT	
	ION	enable interrupts
	NOP	after this instruction interrupts should be enabled, and there is an interrupt 
pending so it should be taken before the final instruction
	HLT	correct execution will stop here having executed the interrupt handler

If interrupts are implemented correctly, this program should halt at the end having 
executed the interrupt handler once. Otherwise it should stop near the instruction in 
error. This program did not show up the problem with the SKON instruction which was 
detected by DEC's diagnostic tests. 

The interrupt handler used contained instructions to ensure that the contents of IF and 
DF had been correctly saved on taking the interrupt. Interrupts must also have been 
correctly disabled before entering the handler: if they had not been, the emulator 
would have made repeated attempts to take the interrupt by jumping to location 0001 
at the start of each cycle.




4.1.6  Extended Memory Instructions

A PAL-8 program was written to copy a sequence of words from field 0 to field 1. 
This checks that the data field can be changed correctly. The sequence of words 
constituted a program, which was then run by issuing the instructions:

CIF 1
JMP START

This checks that the new instruction field is correctly buffered until the JMP instruction 
is encountered. In fact, a slightly more complex sequence was used which involved 
simulating an interrupt between the two instructions above. This interrupt was not 
taken until immediately after the JMP instruction, demonstrating that interrupts are 
correctly inhibited by the CIF instruction and uninhibited by the JMP instruction.

The rest of the extended memory instructions were then checked by executing a 
program containing them in single step mode and checking the system state between 
steps.



4.1.7  Teletype and Paper Tape Devices

To test these devices, programs were written to read and write sequences of 
characters. For each action (read or write) and for each device, two programs were 
required. One of the programs used a 'busy waiting' loop to wait for the next character 
to be ready. (Or for the simulated hardware to be ready for the next character when 
writing.) The program tested that this technique could be used to read (or write) a 
string of characters. The other program tested the device's interrupt instructions by 
using an interrupt handler to perform the character processing: the main program does 
something else while waiting. 

Success in all cases was denoted by the production of a string of characters either in an 
output file or in a previously empty memory block.

The biggest problem encountered was with reading characters. This is because the 
emulator must wait at least until the character reader thread is next scheduled before 
the next character can be read. This potentially wastes many instruction cycles: initially 
it was unclear that the interrupts were working, as a large number of cycles was 
executed before taking an interrupt. A possible solution is for the processor thread to 
block so that the character reader can request a character from the input. Care would 
be needed to ensure that the processor thread was unblocked and able to continue if 
the character reader thread blocked while waiting for the character.

Other minor problems were encountered and their solutions are described in 
section 3.8.


4.1.8  DEC's Diagnostic Tests

The emulator was set up to run overnight without a user interface and was run using 
the command:

nohup pdp8e -i test.init &

This combination ensures that the process is not stopped when the user logs out. 
Teletype output was redirected to a file. Success was indicated by a file of constantly 
increasing size containing either As or Bs. 

Each test was only able to highlight one problem at a time as they are deterministic 
programs indicating faults by halting near the instruction which is in error. They did 
indicate small errors in the implementation of the SKON, RTF and GTF instructions as 
described in section 3.6, the error in the TAD instruction as described in section 4.1.3 
and the error in the IAC instruction as described in 4.1.4.

Once the errors were corrected, the test programs ran continuously as expected.



4.1.9  Running u8-388 and FOCAL

The final verification test was to get the emulator to run some meaningful programs. 
The first was the calendar program (u8-388), which correctly produced a calendar for 
this year when presented with 1995 as its input.

Running FOCAL was more difficult, for the reason described in section 3.8. Once this 
problem had been ironed out, FOCAL ran correctly, though very slowly. The 
following program was run within the FOCAL interpreter, showing that it is possible 
to run a program within a program (FOCAL) within a program (the emulator)! The 
emulator was, of course, supported by many other programs in the form of the Unix 
operating system. The FOCAL program was:

1.1 TYPE "HELLO WORLD!"!
1.2 GOTO 1.1

The trailing ! on line 1.1 ensures that a carriage return is produced. To test FOCAL's 
mathematical functions, the command

TYPE FLOG(2)

was used. This correctly returned the result

=    0.6932




4.2  Performance Testing

At this stage, a correctly functioning emulator had been produced. However, its 
performance left a lot to be desired. The reason for needing to leave the diagnostic 
tests running overnight was that the second test program only produced a B 
approximately once every hour. (The first test program was no better.) This made the 
new emulator 720 times slower than the original machine, and 240 times slower than 
Jones' emulator.

The Modula-3 execution profiling utility was used to generate a breakdown of the 
number of times each user defined procedure was called while running the calendar 
program. The most frequently called procedures were:


procedure
frequency




diagnostics generator
663898

read a value from a register
317651

write a value to a register
200723

read a value from memory
156576

increment a register value
138053

interrupt status object method 4 
113305

interrupt status object method 2
113305



These results motivated the changes described in sections 4.2.1 - 4.2.4 below. Some 
more general points on execution efficiency are then considered as described in 
sections 4.2.5 - 4.2.6.



4.2.1  Diagnostic Messages

By far the most commonly called procedure is the one which produces diagnostic 
messages. The program written to comment these calls out of the source code is 
described in section 3.9. The calls were not removed completely; another program was 
written to reinstate them for when they were needed for further debugging. The 
removal of diagnostic messages from the program sped it up by a factor of about 10.





4.2.2  Register Accesses

The presence of all of the methods of the register object type in the above list 
suggested that any optimisation performed here would be worthwhile. On reflection, it 
was realised that the register object type  is unnecessary. It was therefore replaced by 
two Modula-3 ordinal types: Register_12 and Register_3, which were defined as 
the ranges [0..4095] and [0..7] respectively. It is still impossible to write invalid 
values to registers represented in this way. The only drawback is that overflow must 
be explicitly anticipated when incrementing the register. The removal of the procedure 
call overhead means that each register access now requires significantly less 
processing.



4.2.3  Memory Accesses

Memory reads, writes and increments were initially implemented as methods of the 
memory object.  Reading from memory was the fourth most common procedure call 
in the table on page 28. To cut down the time taken to read from memory, these 
methods were removed and the contents of memory were instead made directly 
accessible. (The same drawback for incrementing a location applies as in the case for 
registers above.) Memory was also restructured as a set of eight pointers to arrays 
representing fields. This allowed a pointer to the current instruction and data fields to 
be stored alongside the current IF and DF values so that less processing is now 
required for each memory access. Since direct memory accesses are limited to two 
pages within the instruction field, this approach could be taken further by representing 
each field as an array of 32 pointers to pages. Pointers to the current and zero pages 
would then be sufficient to refer to all directly accessible locations. This could speed 
up memory access even further, though it would involve more processing as the page 
pointers would need to be updated each time execution moved into a new page.



4.2.4  Interrupts

The final improvement was in the implementation of the interrupt system, to prevent 
the two method procedure calls listed above from being made in each processor cycle. 
The improvements are described in section 3.6.





4.2.5  Separate Threads

It was also found that the emulator ran faster when no user interface was used.  This 
is natural, as less processing is then needed. However, it does suggest that the use of 
multiple threads in general slows the program down, and that it would have run faster 
if it had been designed as a single thread, in the same way as Jones' emulator. As 
pointed out in section 2.2, separate threads were used to make program design easier.



4.2.6  Best Performance

The changes described in sections 4.2.1 - 4.2.4 above were implemented. The resultant 
emulator was run without a user interface on an otherwise unloaded Solaris 2 machine 
(ursa). This emulator, when running the second diagnostic test program, produced a B 
every two minutes. This is 24 times slower than the original machine and 8 times 
slower than Jones' emulator. This is a very large improvement on the unoptimised 
program.

Despite the figure given above, the emulator runs FOCAL at a perfectly acceptable 
speed when performing simple operations and I would guess that it runs at a speed 
comparable to BASIC on the microcomputers of ten years ago. (e.g. the BBC 
Microcomputer)


Chapter 5:  Conclusions


As demonstrated in chapter 4, a successful PDP-8/e emulator was produced. This 
emulator functions correctly in that it passes DEC's diagnostic tests. It is also capable 
of running the FOCAL interpreter.

Though it runs FOCAL at a tolerable speed , the emulator runs the diagnostic tests 
about 24 times more slowly than the original machine. Given more time it would 
probably be possible to shave this figure to about 20. In the end though, the emulator 
is limited by the language and methods used to construct it.

Modula-3 is a strongly typed language with many built-in run-time checks. It also has 
a garbage collector. While these do help enforce the writing of robust code, they do 
introduce a large execution time overhead. This is a problem for programs such as an 
emulator where speed is a prime consideration. The use of separate threads also 
introduces a time overhead, though the raising of interrupts by devices as and when 
characters are ready would otherwise be difficult to implement.

To achieve an emulator closely approaching the speed of the real machine it would 
probably be necessary to start again from scratch. The emulator could then be planned 
speed as the primary consideration, sacrificing clarity of implementation where 
necessary. It would also be a good idea to use a language (such as C) which does not 
have significant run-time overheads.

A copy of the source for this project has been sent to Douglas Jones and should be 
available via his DEC PDP-8 index.


Bibliography


Brown, M and Meehan, J:	The FormsVBT Reference Manual: version 2.2
	(* DEC 1992-3)

Harbison, S: Modula-3 (* Prentice Hall 1992)

Jones, D:	DEC PDP-8 index: http://www.cs.uiowa.edu/~jones/pdp8/index.html

Jones, D:	PDP-8 Frequently Asked Questions:
	http://www.cis.ohio-state.edu/hypertext/faq/usenet/dec-faq/pdp8/faq.html

PDP-8/e Small Computer Handbook 1971 (* DEC 1970 (sic))

PDP-8 Handbook Series:	Volume 1: Introduction to Programming
	(* DEC 2nd ed. 1970)

Usenet news group: alt.sys.pdp8


Appendix 1:  DEC's Diagnostic Tests


The tests used consisted of two programs which were obtained, along with minimal 
documentation, from Bob Supnik at DEC.
(Electronic mail address: supnik@human.enet.dec.com)

They are repetitive tests which are designed to be run for a long time to check that the 
hardware is functioning correctly. If they detect an error, they halt the machine at an 
address close to the instruction at fault. On the original PDP-8/e the tests rang the 
teletype bell every five seconds as a sign that they were still running. (On the versions 
used here this has been replaced by typing a letter every five seconds: A in the first test 
and B in the second).

The speed at which these tests ran on an emulator was used as a benchmark when 
assessing performance.


Appendix 2:  Instruction Formats


The basic instruction format is shown in figure 4.


 



A2.1  Memory Reference Instructions

The layout of a memory reference instruction (AND, TAD, ISZ, DCA, JMS or JMP) is as 
shown in figure 5.

Memory is divided into 8 fields each containing 32 pages of 128 words. The 
instruction field (IF) and data field (DF) registers specify the two currently accessible 
fields. The program counter (PC) always refers to an address in the current instruction 
field.

An instruction may directly address any location in the current page or in zero page of 
the instruction field, and may indirectly address any location in the data field (for AND, 
TAD, ISZ or DCA) or in the instruction field (for JMS or JMP).



A2.1.1  Indirect Addressing

If the I bit is set, the address specified contains a pointer to the address to be referred 
to by the instruction. The instruction type determines whether this address is in the 
data or instruction field as specified above. In each field the locations from 0010 to 
0017 (octal) are autoindexing. This means that when referred to as a pointer when 
indirect addressing is used, their value is incremented (and written back to the 
location) before it is used to specify the actual address to be referred to by the 
instruction.



A2.1.2  Page Specification

If P is zero then the following 7 bits refer to an address in zero page of the instruction 
field, otherwise they refer to the page of the instruction.




A2.2  IOT Instructions

These have the format as shown in figure 6.


 



The device selector bits indicate the device for which the instruction is intended. The 
three command bits specify the command, whose meaning depends on the individual 
device.



A2.3  OPR Instructions

These are divided into three groups. Within each group, each bit specifies a different 
action to perform. If multiple function bits are set, then their actions are combined. 
There is a prescribed order in which the actions are combined. This is specified  to 
make the combined instructions as useful as possible.

Group 1 has the format given in figure 7 and is primarily concerned with manipulating 
the contents of the AC and link registers. (The link is one of the bits of the flag register 
and is used as a carry flag.)


 



Group 2 has the format given in figure 8. The instructions in this group provide 
conditional execution and a few other miscellaneous functions. The conditional 
execution is provided in the form of instructions which cause the next instruction in 
memory to be skipped if conditions on the AC or link registers are met.



For example, to branch if AC is zero, the following code is used:

SNA	skip next instruction if AC is not zero
JMP address
...other code...	code to execute if AC is not zero


The layout of group 3 is shown in figure 9. The only functions in group 3 which are 
implemented on the basic machine are transfers between AC and MQ. The other 
functions in this group are only implemented if the extended arithmetic element is 
present.


Appendix 3:  Initialisation File


On startup it is possible to specify an initialisation file for the emulator using the 
command:

pdp8e -i <filename>

If no file is specified or if the file cannot be found then default values are used.

The file is split into sections so that different parts of the emulator can easily identify 
the parameters intended for them. A new section is denoted by the string 
[<section name>] at the beginning of a line. A line beginning with a # is ignored.

This idea is taken from the form of initialisation files used by Microsoft Windows.


The sections are as follows:

[AutoExec]
InstructionLimit=<number>	limit instructions executed to this number
UserInterface=Graphical|Keyboard|None	specify user interface type
StartAddress=<octal>	start at this address in field 0
SwitchRegister=<octal>	start with switch register set to this value
AutoExec=TRUE|FALSE	start execution immediately and automatically

[CharReader]
Delay=<number>	number of seconds (as a LONGREAL) to wait
	between accepting characters from the input 
	stream

[DiagSetup]
Stdout=TRUE|FALSE	display diagnostics on standard output
File=<filename>	write diagnostics to specified log file

[Diag]
<type>=TRUE|FALSE	turn on|off diagnostics messages of this type

[IOFiles]
Keyboard=<filename>	use this file as keyboard input
Teletype=<filename>	use this file for teleprinter output
Reader=<filename>	tape reader input file
Punch=<filename>	tape punch output file

[Memory]
InFile=<filename>	initialise memory from this file
OutFile=<filename>	dump memory image to this file on completion


Appendix 4:  Interrupts


An interrupt request may be issued by any device on the system bus. Servicing an 
interrupt consists of storing the current value of the PC in location 0000 in field 0, 
disabling interrupts, saving the current values of the IF and DF registers and 
transferring execution to location 0001 in field 0. Interrupts are enabled by the ION and 
RTF instructions, but there is a one cycle delay before an interrupt will be taken. This 
allows interrupt handlers to return before re-enabling interrupts, and is necessary 
because subroutines are not re-entrant as there is no return address stack.

Interrupts may also be inhibited. This occurs when the IB register is loaded with a new 
instruction field, and prevents interrupts from being taken until the next JMP or JMS 
instruction when the new field is transferred to the IF register. This method of 
changing the instruction field makes it easier to write programs, as the field change is 
delayed until the next JMP or JMS instruction. Inhibiting is necessary so that a JMP or 
JMS instruction occurring in interrupt handling code does not upset the field registers, 
which would cause execution to return to the wrong field.

Each device has its own flag which is set when it issues an interrupt request, and 
provides an instruction which skips the next instruction if this flag is set. It is the 
responsibility of the interrupt handling code to ensure that the device clears its 
interrupt request and that it re-enables interrupts if appropriate.


Appendix 5:  User Interface Commands


The commands recognised by the keyboard user interface are as follows. The front 
panel key to which a command is equivalent is given in parentheses.

AL: (ADDR LOAD)	set current address from the switch register
CL: (CLEAR)	clear AC, link, interrupts, I/O flags
CO {R|W} {K|T} <filename>*:	connect specified file to Reader | Writer of 
teletype(K) | Tape reader
DC {R|W} {K|T}:	disconnect file
DE: (DEP)	deposit value in switch register at the current 
address, and advance the address by one
DI <1-5>:	equivalent to settings of the selector on front 
panel: selects the extra information to display as 
described below.
EX: (EXAM)	examine value at current address, and advance the 
address by one
GO: (CONT)	start continuous mode execution
HA: (HALT)	stop continuous mode execution
HE:	print this information
QU:	stop emulator, tidy up and exit program
SR <xxxx>	set switch register
SS: (SING STEP)	execute single processor cycle
SW:	invert SW line
XL: (EXTD ADDR LOAD)	set the current instruction field from bits 6-8 of 
switch register and the current data field from bits 
9-11 of switch register.


The implementation of the display modes for the second row of indicator lights differs 
slightly from the original hardware as many of the original signals have no equivalent 
on the emulator. The implementation used is as follows:

1: STATE:	bits 3-5:	opcode of current instruction
	bit 8:	state of the SW line
2: STATUS:	bit 0:	link register
	bit 2:	flag whether interrupts are pending
	bit 3:	flag whether interrupts are inhibited
	bit 4:	flag whether interrupts are enabled
	bits 6-8:	instruction field register
	bits 9-11:	data field register
3: AC:	contents of the accumulator
4: MD:	contents of the memory buffer register 
5: MQ:	contents of the memory quotient register


Appendix 6:  Paper Tape Formats


The two paper tape loading programs available with the PDP-8/e are Read-In Mode 
(RIM) and Binary (BIN). The RIM loader is a very simple program which can easily 
be loaded into the machine by hand from the front panel. This is then usually used to 
load the BIN loader which is able to read tapes which were punched using the more 
compact BIN format.

Paper tapes were eight bits (or channels) wide: the lower six bits were used to specify 
addresses and data, the seventh and eighth bits were used for control data. A hole in 
the tape denoted a binary one. A hole in the seventh tape channel indicated the first 
byte of an address. A hole in the eighth channel indicated leader or trailer tape. There 
was a ninth, small hole which was punched between the third and fourth channels in 
every row. This was the sprocket hole used to guide the tape through the reader.



A6.1  RIM Format

Each data item is specified by two pairs of bytes on the paper tape. The first pair gives 
the address and the second pair specifies the value to be placed at that address.

The beginning of a RIM tape might be punched as follows:


Channel:
8
7
6
5
4
S
3
2
1

















1
0
0
0
0
.
0
0
0

leader tape


1
0
0
0
0
.
0
0
0




1
0
0
0
0
.
0
0
0

















0
1
0
0
0
.
0
1
0

address: 0200 (octal)


0
0
0
0
0
.
0
0
0

















0
0
1
0
0
.
0
1
1

value: 4317 (octal)


0
0
0
0
1
.
1
1
1

















0
1
0
0
0
.
0
1
0

address: 0201 (octal)


0
0
0
0
0
.
0
0
1

















0
0
1
0
1
.
0
0
0

value: 5026 (octal)


0
0
0
1
0
.
1
1
0








A6.2  BIN Format

BIN is more compact because it allows continuous blocks of values to be specified. If 
no address is specified it is assumed that the next value is to be placed in the next 
sequential memory location.

The beginning of an equivalent BIN tape might be:


Channel:
8
7
6
5
4
S
3
2
1

















1
0
0
0
0
.
0
0
0

leader tape


1
0
0
0
0
.
0
0
0




1
0
0
0
0
.
0
0
0

















0
1
0
0
0
.
0
1
0

address: 0200 (octal)


0
0
0
0
0
.
0
0
0

















0
0
1
0
0
.
0
1
1

value: 4317 (octal)


0
0
0
0
1
.
1
1
1

















0
0
1
0
1
.
0
0
0

value: 5026 (octal) to be placed at


0
0
0
1
0
.
1
1
0

address 0201 (octal)



Note that the second address does not need to be specified.


Appendix 7:  Removing the Diagnostics


Below is the part of the lex script which recognises the appropriate tokens in the 
source file. The main program which deals with processing the source files has been 
omitted.


%{

/* various #include statements here */

#define FALSE 0
#define TRUE  1

/* flag whether currently processing a call to be commented out */
int commenting = FALSE;

/* code returned if an irretrievable error is encountered */
#define ERROR 1


%}


bra	\(
ket	\)
semicolon	\;
star	\*
opencomment	{bra}{star}
closecomment	{star}{ket}
procedure	Diag\.Message
endstatement	{ket}{semicolon}


%%


{opencomment}{procedure}	{	/* already commented Diag.Message */
		/* echo to output */
		fputs("(*Diag.Message", yyout);
	}


{procedure}	{	/* not commented Diag.Message */
		/* insert comment and note that */
		/* we're processing a Diag.Message command */
		if (commenting == FALSE) {
			fputs("(*Diag.Message", yyout);
			commenting = TRUE;
		}
		else {
			fprintf(stderr, "error\n");
			return ERROR;
		}
	}




{endstatement}{closecomment}	{	/* end of already commented call */
			/* echo to output */
		fputs(");*)", yyout);
	}


{endstatement}	{ /* end of statement */
		fputs(");", yyout);
		/* if processing a call to be commented */
		/* out, output closing comment and note */
		/* that comment has finished */
		if (commenting == TRUE) {
			fputs("*)", yyout);
			commenting = FALSE;
		}
	}


%%


int main (int argc, char **argv)
{
	/* the main program follows here */
}




Project Proposal


Emulating a DEC PDP-8/e

Suggested by Dr LC Paulson

Resources required: increase of CUS disc quota to 11Mb

Supervisor: Dr LC Paulson
Director of Studies: Dr LC Paulson

Overseers: Dr M Richards, Dr NE Wiseman


Description of Work

The project is to write a program in Modula-3 which will emulate the Digital 
Equipment Corporation PDP-8/e. The front panel will be implemented graphically 
using the Trestle user interface system for X Windows. The program will be compiled 
on a Solaris 2 machine (Ursa) and run under Unix.

The program will emulate the base model PDP-8/e, including a 4096 12-bit word main 
memory with teletype (via computer keyboard and X Windows window) and paper 
tape punch and reader (with the paper tapes emulated by standard files on the Unix 
filing system) as well as the front panel. Other options could be implemented if time 
permits.

Intermediate stages will be tested using test harnesses and diagnostic messages which 
are optionally activated at run-time. The finished emulator will be tested using existing 
test programs which are available on the World Wide Web.

Evidence of success would be supplied in the form of session records and memory 
dumps, as alluded to below.





Resources Required

My estimate of the CUS workspace needed is as follows:

(assuming source files of size about 10kb, and object files of size about 12kb, and 
executables of size about 300kb)


    kb

100 source modules and associated interfaces x 3
3000

100 object files
1200

a few (3) executables
900

documentation
500




total
5600


The multiplier of 3 is to allow for the use of the RCS version control system to track 
changes in the source code.
Hence, as I also expect to use CUS for course work programming examples later in 
the year, and as my usage already stands at about 2.2Mb, I propose that I need an 
additional quota of 6Mb on top of my current quota of 5Mb, making a total of 11Mb.



Starting Point

I have no knowledge of the PDP-8/e prior to this year.

There are a few existing emulators written in C available on the World Wide Web. 
These will be used to evaluate existing test programs which are in BIN loader form 
and have been obtained from the United States. However, the intention of the project 
is to start from scratch in Modula-3.

I have available (from the World Wide Web) a cross-assembler and a copy of the BIN 
loader which runs on a PDP-8/e (emulator) and accepts the assembler's output and 
loads it into the core of the PDP-8/e. These will be used to run test programs as 
necessary.





Work Plan


5th to 16th December (2 weeks, no lectures)

Planning and design of the project including decisions about the execution threads 
required and about the data structures and methods to be used.
Also investigate:
  - the feasibility of using FormsVBT to implement the interactive front panel display
  - possible methods of implementing the instruction table
  - the feasibility and desirability of implementation of various extra features

Implementation of the basic CPU cycle and a minimal instruction set. Include in the 
program conditionally available diagnostics messages so that the behaviour of the 
instruction cycle can be traced and so that tests can yield concrete results.


17th December to 8th January: Christmas

9th to 20th January (2 weeks, no lectures)

Implement a simple control interface to allow the running of the emulator from the 
keyboard. This will require commands to read and write specified memory locations, 
the ability to start and stop execution, and the ability to load and save core memory 
from/to disc.
The first test of this interface should be using a harness which simply reports on the 
commands given to it by the interface. This harness should be kept for use with the 
graphical interface.

For further testing at this stage the following abilities are required:
  - dump sessions to file
  - be able to generate a dump of a saved core memory file
  - be able to load programs directly into the emulator, via the ability to load core 
    memory
  - diagnostics output from the program as it runs


21st January to 3rd February (2 weeks, with lectures)

Implement high speed tape reader and punch and rudimentary teletype facility, and the 
CPU instructions required to support them. Implement diagnostics within the CPU 
instructions to allow easy testing. Test and print test results.
Both should be implemented with files as the source/sink for information. This is not 
the final intended implementation for the teletype but will allow instruction tests to be 
performed which output to teletype.




4th February to 3rd March (4 weeks, with lectures)

Implement the rest of the core PDP-8/e instruction set (i.e. that considered necessary 
for a minimum implementation) plus extra instructions as time permits. Test the 
implementation. By this stage it should be possible to test using third party test 
programs.

NB Progress report due at the end of this period (3rd March)


4th to 31st March (4 weeks, two weeks with lectures)

Implement graphical front end:
  - design the appearance of the interface
  - provide functionality by associating procedures with the items on the graphical front 
panel
  - provide a way to 'plug in' this functionality to the existing front panel

Initial testing of this stage is by use of the test harness used for the simple control 
interface. Further testing should be by hooking the front end to the rest of the system 
and checking that key presses perform as expected.


1st to 23rd April: Easter

24th April to 5th May (2 weeks, with lectures)

The above continued.


6th to 12th May (1 week, with lectures)

Implement teletype input from keyboard and output to screen. This is dependent upon 
the success of the graphical front end - which then frees up the main window for use 
as the teletype output. Implement the few extra commands required. Test and 
document test results.


15th May to 18th June: break for revision, exams and celebrations

19th to 30th June (2 weeks, no lectures)

Final testing of program and implementation of other features as time permits.


1st to 30th July (1 month, no lectures)

Write dissertation, print and submit.
 The word size was 12 bits.
 Jones: PDP-8 Frequently Asked Questions: What is a PDP-8?
 Introduction to Programming, page 7-54
 See appendix 6 for details of RIM and BIN tape formats.
 See appendix 1 for details.
 See appendix 2 for full details of instruction formats and memory addressing.
 See section 4.1
 See sections 3.6 and 4.1.4.
 From sources found via Jones' DEC PDP-8 Index.
 A picture of the graphical user interface for my emulator can be found between pages 12 and 13.
 See appendix 1.
 PDP-8/e Small Computer Handbook 1971
 PDP-8 Handbook Series: Volume 1: Introduction to Programming
 See section 4.2.5.
 This holds the opcode of the current instruction.
 All transfers to and from memory pass through this register.
 This is an internal record of the current values of the switch register switches on the front panel.
 This is a one bit register which is set by the SW key on the front panel. On the original machine 
this key was connected directly to a line on the system bus.
 As described in appendix 2.
 The FormsVBT Reference Manual, page 60.
 See appendix 3.
 See appendix 6.
 See appendix 3 for details of the initialisation file.
 See section 4.2.2.
 See section 4.2.3.
 This feature is mentioned in section 2.5. The maximum number of instructions is an option which 
may be specified in the initialisation file: see appendix 3.
 See appendix 2 for details of memory addressing.
 Hence JMS can read the return address directly from the program counter.
 Either by the user or because this was a HLT instruction.
 Defined in appendix 5.
 See section 2.4.
 As described in appendix 5.
 See figure 2.
 See appendix 3.
 See section 3.3.
 See section 2.5.
 See appendix 2 for details of instruction formats.
 As described in chapter 1.
 For exact details of individual OPR instructions see the Small Computer Handbook, pages 3-5ff and 
4-25ff.
 Defined in the Small Computer Handbook, pages 4-27ff.
 See section 2.3.
 See appendix 2 for details.
 This method is separate from the first to simplify the test to decide whether to take an interrupt 
which is made in the processor cycle.
 See section 4.2.
 This would be doubled by using a counter of type INTEGER and starting at -232. This would 
complicate the specification in the initialisation file of the maximum number of instructions to 
execute.
 See appendix 4.
 Interrupts ON
 Small Computer Handbook, page 3-14.
 Having discovered what the correct implementation was by referring to Jones' emulator and the 
news group alt.sys.pdp8.
 See appendix 4.
 The teletype's maximum speed was ten characters per second (cps); the paper tape reader read at a 
maximum of 300 cps. Therefore, with an average instruction time of 2.6 *s, more than 1000 
instructions could be executed between characters when reading from paper tape.
 The length of time can be varied between runs by specifying it in the initialisation file.
 See section 4.2.
 This is the token recognition function produced by lex.
 As described in section 2.5.
 See appendix 5.
 See appendix 2.
 See appendix 4.
 See section 4.1.5.
 See section 4.1.6.
 See section 3.5.2.
 According to Jones' emulator and information obtained via the news group alt.sys.pdp8.
 See Small Computer Handbook, page 3-6.
 The instructions which are not executed if the implementation is correct are marked *.
 See section 3.6 for a description of this problem and its solution.
 Here it just counted to a very large number.
 See appendix 1 for a description of DEC's tests.
 See section 3.6 where the interrupt object methods are described.
 As described in section 3.1.
 See section 3.1.
 The reason for providing this facility is given in section 2.5.
 As noted in section 4.2.6.
 Small Computer Handbook, pages 4-27ff.
 There is a bug in the implementation here as the memory buffer register is usually ignored for 
reasons of speed so its contents are seldom updated.


Proforma

Table of Contents

Chapter 1: Introduction

43

42

Chapter 2: Preparation

Chapter 3: Implementation

Chapter 4: Evaluation

Appendix 2: Instruction Formats

Appendix 6: Paper Tape Formats

Appendix 7: Removing the Diagnostics

Project Proposal


