Subject: Bill Haygood's pdp8 emulator

From: [email-masked] (Bill Haygood)
Date: 29 Dec 1993 02:29:36 GMT

I thought the following conversation contained some interesting points
about pdp8 emulation that other pdp8 lovers might find interesting.

On Tue, 1993 Dec 28, Charles Lasner wrote:
> 
> 
> On Tue, 28 Dec 1993, Bill Haygood wrote:
> 
> > On Tue, 1993 Dec 28, Charles Lasner wrote:
----- One of the key points I make against using the RK8E in an emulator is 
----- its (unnecessary) complexity.  Just how much of the beast is emulated? 
----- A true emulator has to pass all of the relavent diagnostics, including
----- that maintainence IOT.  Do you really expect to simulate all the proper
----- timings of all of the RK registers shiftable a bit at a time?
> > 
> > I have made no attempt to emulate every register of the RK8E.  My emulator
> > does not support IOT 6747.  Rather, I attempted to emulate sufficiently 
> > that a file with sector by sector contents looking like an RK05 pack could
> > boot.  I have never had interest in simulating timings of the actual
> > hardware.  In other words, if my PT8E runs twice as fast as the actual
> > hardware, then I just pretend I have a PT8E that reads at 600 cps. 
> > Obviously, this may trip up those MAINDECs that check for these kinds of
> > timings.
> 
> So, I take it that you are certain that none of the conditions that look
> like "errors" can happen, and every normal combination of disk address
> info can be passed to DLAG to start a transfer, including setting the
> one-page transfer bit.  I also assume that DLAG leads to immediate
> completion of I/O, and to DSKP skipping, etc.  Thus, interrupts can only
> occur on non-I/O instructions when the code running is the OS/8 handler.

Some errors do get flagged.  See 'pdp8misc.c' for details in routines
whose names start with 'rk8'.  I have re-written some of this based on
notes received from Bob Supnik.  I have not taken the time to completely
implement interrupt service for the RK8E emulator.  Obviously, I have not
taken time to think through all the issues.

> Do you treat attempts to access the disk past the end of RKB0: as an
> error?

Yes, in that sense, I tried to make it look like a real RK05 pack.

> A "real" RK only has 6496 sectors, so the handler is organized as
> 3248 RKAu: and 3248 RKBu: where u is 0-3.  By patching PIP, RKAu: can be
> 4096 blocks, and it's real easy to start RKBu: on record 4096, resulting
> in a much smaller size, but the first one is full-size.  (Alternatively,
> the system area could be made smaller so that the B portion was 4096, but
> I doubt if anyone bothered; I do know that the former was done though!) 
> Thus, it can be argued that an "authentic" emulator wants to have errors
> at record 6496, but a "useful" one would allow up to record 8191.  That
> would allow a 4096 record A and a 4096 record B as well.  If it's an error
> to access record 6496 and beyond, then you have to emulate all of the
> error recovery correctly, including the recalibrate sequence,
> heads-in-motion bit, and the more general resets.  All of these things can
> come about in practical handler code that exists.  By allowing a larger
> disk, the error stuff can all be avoided.  (If you have a good data scheme
> for the pack data, then only rarely would blocks past the end get
> referenced, so most could be dormant unless the handler accesses all of
> them.  But the standard handler will occasionally have to deal with the
> boundary condition since there are bugs in various programs that allow
> access to non-existant blocks.  BTW, on the DECmate, to avoid the problem,
> the logical size of the RX50 is indicated deliberately slightly too small,
> to avoid destruction of the "slushware" tracks further up on the device. 
> In that case, the records physically exist, but would be endangered by one
> of these programs that accesses too far in case it wrote there!

I aimed to make RK05 'pack' files portable across big- and little-endian
architectures by treating them as byte streams and storing the pdp8 12-
bit words in big-endian order with no gaps between words.  Thus, I store
2 pdp8 words in 3 bytes in sequential high-to-low bit order.  While this
may slow the I/O somewhat, the emulator allocates a 32-sector (one cylinder)
buffer for each "RK05 drive" and all reads and writes occur to that buffer
until the emulator need access to a new cylinder.  The emulator maintains a
"dirty" flag for each buffered cylinder for each "RK05 drive".  In this
manner, the emulator does not write the "old" cylinder back to disk unless
a "write" occurred to that cylinder from the RK8E emulator.

------- 
------- 	On my Amiga, I deleted the old 'pdp8.ram' and brought up the
------- 	emulator with the following command:
------- 
------- 	pdp8 -rk8e pack0 -boot
------- 
------- 	Then I executed the following OS/8 commands:
------- 
------- 	.DA
------- 	.DEL CLEAR.CF
------- 	.QUIT
------- 
------- 	The '.QUIT' causes the emulator to exit with a 'pdp8.ram' file
------- of about 11K.
----- 
----- What determines the size of that file; thus what was so particular about
----- this one?
----- 
> > 
> > The contents of the 'hardware' registers and the contents of memory
> > (after compression) determine the contents and size of 'pdp8.ram'.  The
> > 'pdp8.ram' referred to above contained nothing special except that I could
> > compare it to the 'pdp8.ram' file on my Amiga after the two versions of
> > the emulator executed identical commands.  This just proved to me that the
> > emulator instruction executions resulted in identical memory conditions at
> > the conclusion of each instance of emulation.  Thus, I think that the two
> > versions of the emulator agreed on CPU emulation.  So, I could profitably
> > spend time looking at IOT and RK8E emulation.
> > 
> > In other words, after identical executions on the two versions of the
> > emulator (Amiga and BBS), the 'pack' files contained the ONLY differences.
> > Hence, I believe the RK8E emulator contains the source of the problem.
> 
> While I agree with your reasoning, please note that OS/8 doesn't save
> memory that much, thus you are largely looking at the keyboard monitor in
> all cases!  There isn't much difference between identical takes on the
> same machine even if you interchange the order of some of the commands, etc.

I agree, but I looked for any straw I could grab!

----- I assume you wrote some hack QUIT.SV that executes some reserved IOT to
----- exit out of the emulator.  If so, what's in it?
----- 
> > 
> > Jim Van Zee implemented QUIT in the CCL.  It simply loads a HLT into
> > location 07577 and jumps to it.  This allows a restart with a '-cont'
> > option at 07600 to restart OS/8 without a reboot.
> 
> What you are saying is that the emulator can be started on a system with
> the memory contents defined already?  If so, is this the same as a real
> machine with core memory in that sense, and not a specific kludge for
> OS/8?  Thus, if memory of say, field 7 and field 0 where setup
> appropriately, a restart at some location, such as 00353 could be done?

Yes, yes and yes.  Just prior to exit, the emulator compresses all 32K of
pdp8 memory and stores the compressed image in the 'pdp8.ram' file.  Worst
case compression: 52K, best case: 4 bytes.  I designed the compression
method for speed.  At next invocation, the emulator unpacks the 'pdp8.ram'
file and a user option of, say, "-cont 353" or "-pc 353 -cont" would
"continue" pdp8 instruction execution at location 00353.  A "-clear" option
would "depress the CLEAR switch" also, if needed.

----- [remainder deleted]
----- 
----- cjl
----- 
> > 
> > I hope this proves helpful.
> 
> Certainly has.
> 
> cjl
> 
> 
> 
> 

Since submitting the sources to Charles Lasner for anon-ftp on sunsite, I
have completely rewritten the EAE to make it more efficient.  The new pdp8
initialization sequence loads the instruction vectors for EAE only if the
user specified the EAE as an option (-eae).  Doing it this way does away
with all those "if (EAE)" statements, thereby speeding EAE emulation.  If
your compiler provides for automatic in-lining of functions, I recommend
compiling the EAE emulator (pdp8eae.c) with that option.  Until I feel
assured that I have the EAE "right", I want to leave some functions to
call other functions (e.g., the function which executes "SCA MUY" only
does the "SCA", then calls another function to do the "MUY").  Later, I
can re-write it for much greater efficiency.

I would appreciate some info on "RX50".  My absence from and inattention to
the pdp8 world for so many years has left me ignorant of the RX50.  I
would appreciate any specifics you may have.  If this has the potential
of providing us with a common medium for both the actual hardware and on
machines running emulators, then I must undertake a study of how to
access the floppy drives on my Amiga.  Since I have a program to enable
reading/writing of a 3 1/2" floppy in MS-DOS format, this tells me that I
can do the same for a pdp8 formatted 3 1/2" floppy--provided I can find
the necessary documentation.  (The Amiga documentation exists--but I do
not understand the need for so much complexity [e.g. the explanation on
how to access the serial port takes 12 pages].)

--
Bill Haygood

    "I absolutely reject absolutism."

---

From: [email-masked] (Charles Lasner)
Date: 30 Dec 1993 19:37:59 GMT

In article <[email-masked]>,
Bill Haygood <[email-masked]> wrote:

>>        Thus, if memory of say, field 7 and field 0 where setup

The question about field 7 and field 0 00353 is not a random one.  This
means that P?S/8 for RK8E should also work under the same emulator!  The
P?S/8 memory convention is that the system is restarted at 07600 just as
in OS/8.  However, memory is not saved ever by this operation, and thus
more resembles the OS/8 specific of restarting at 07605.  (There is no
need to save memory, since that function can be handled from ODT; all of
field 0 is automatically saved in the two scratch files named % and $ by
ODT before exiting to the keyboard monitor.  Thus, the vast majority of
program exits to 07600 avoid the OS/8 overhead of needlessly saving memory
before reloading a downsized keyboard monitor that only occupies 1K and
only has 9 commands, and then requires swapping in CCL to execute anything
else!  The P?S/8 keyboard monitor is 4K long including a built-in line
editor and file buffer, etc.  When a program is loaded or debugged, etc.,
it can be controlled by ODT for core-save purposes.)

When the specific RK8E version of P?S/8 is restarted at 07600, the
keyboard monitor is read in from the disk into 00000-07577.  This includes
a copy of the file $ if optionally enabled (to allow editing a small text
file file auto-save and auto-load).  Then the keyboard monitor is
restarted at 00353.  The system handler that accomplishes the read/writes
resides partially in 07600-07777.  The rest is in field 7 in 6000-7777. 
The reason for so much code space is that this is the worst-case allowance
for "problematic" devices such as the RK8E.  (Note, the RK isn't all that
troublesome!  However, reserving this much space allows a handler to be
written for such a problematic case as a DECmate II,III,III+ RX50
byte-mode system handler, a case OS/8 cannot handle at all!  On these
machines, OS/8 allows only 12-bit-mode handlers, which wastes 25% of the
disk space, and makes the ENCODE and DECODE programs useless for the
purpose of transferring entire RX50 images of other systems' disks.  Thus,
only an ENCODEd OS/278 disk image can be used there, etc.  I am working on
a P?S/8 non-system handler for the byte-mode case that would be callable
from OS/278 as a "TSR" to use the MS-DOS term for it.  That would
indirectly allow OS/8 to be upgraded for the RX50 handling purpose, etc. 
P?S/8 non-system handlers are allowed to be up to an entire field.  Unlike
OS/8 handlers that are restricted to one or two pages and must run in
field 0, P?S/8 handlers can be up to 32 pages long, and are page *and*
field relocatable.  Thus, a P?S/8 byte-mode RX50 handler could totally
reside in some part of field 7 under OS/278.  OS/278 has the means of
limiting access to programs regarding memory size, thus "protecting" the
P?S/8 handler now resident in field 7.  A dummy handler can be written for
OS/278 that comfirms the existence of the P?S/8 handler in field 7, and
calls it with a transcribed copy of the arguments of its own caller. 
Thus, OS/278 is faked out into believing it can handle "foreign" devices,
in this case all 400K bytes of a complete RX50 instead of 3/4 of that
amount, where the data is "shredded" every 16 bits, i.e., only 12 of every
16 bits are accessible, and the 4 wasted bits are zeroed upon every write
operation.)

P?S/8 system handlers come in two flavors: the simple case where the
entire handler fits into 07600-07777 as does usually the corresponding
OS/8 handler, and the "problematic" case where it also is given access to
6000-7777 in the highest field available.  (Which is usually field 7; it's
the handler's responsibility to determine the proper field and
self-relocate at boot time.  The only assumption made is that field 1 is
available at boot time to initially load into, etc.)  Thus, the RK8E
version will typically wind up in 76000-77777.

The reason for the "troublesome" aspect of the RK8E for P?S/8 purposes is
that P?S/8 addresses logical blocks to the nearest half-record on an RK8E
while OS/8 "lucks out" and addresses to the physical record.  (Note: both
systems support page-oriented handler calls.  However, OS/8 cannot access
a second-half record only; the record can only be accessed to the first
half or both halves.  Additionally, writing on the first half of an RK8E
record will write 0000 to all of the words in the other half.  This action
only comes about as a consequence of SAVE commands with odd-page-size
arguments.  OS/8 strictly forbids any attempts to access the second half
of a record, thus the SAVE command takes arguments such as 000-177 or
000-377, but *never* 200-377!  As thoroughly documented, any reference to
the second page forces reference to the first page as well, thus specifying
200-377 is taken as if it was 000-377!  Thus, all load and save operations
can ask for odd page counts, but only on a first-half record boundary
basis.  Thus, the only requirement placed on the handler is that should a
record be written on the first half of a logical record, the entire record
is also readable at a later time without error.  The contents of the
second half of the record are allowed to be destroyed when writing the
first half!  The hardware of the RK8E allows this operation by setting a
specific one-page bit.  Reads will inhibit DMA transfer of the second
page; only CRC calculations will be performed on that data.  Writes will
write 0000 on the second page portion of the record, etc.)

Thus, the P?S/8 handler maintains a buffer within the handler.  This is
used to access the appropriate half of any record involved in a half-sized
transfer.  Note that the RK8E hardware one-page bit does resolve the case
of a request for the first half of the record.  To read the second half,
the entire record is first read into the buffer, then the second half data
is moved to the caller's transfer address.  Writing is more complex: first
the affected record is read into the buffer, then the caller's data is
moved from the caller's transfer address to the appropriate half of the
buffer, then the entire record is written out, thus preserving the other
half of the buffer not involved logically with the latest operation.

Any portion of the caller's disk request that involves either entire
record reads or writes are handled identically to the OS/8 handler; normal
DMA read or write is used specifying the caller's transfer address for the
transfer, etc.  Thus, for disk requests with "good" boundary conditions
such as most file operations, there is no special overhead associated with
this.  (The P?S/8 file structure happens to always start on an even
boundary and file buffers are even multiples, etc.  However, some
programs, such as ODT, could require an odd-length call, etc.)

The code that accomplishes all of the requisite operations fits
comfortably in two pages next to the two-page disk buffer.  Thus, there
are 4 free pages in the system handler.  Future versions may support
throughput enhancement such as an elementary cache, etc.  (This would
improve performance in several situations: P?S/8 DUMP and/or BLKODT can be
used for disk patching.  THe programs read in a logical block, operate
on it in their own buffer, then write out the logical block.  The handler
always does each of these operations ignorantly.  However, the data to be
written out just happens to still be in the disk buffer; it need not be
read in again before being written out with the new caller data in one
half, since the other half is still present from the previous read!  Other
programs perform sequential reads into a one-page buffer.  Thus, the data
for the other half is accessed and wasted.  If the cache were enabled, the
data for the other half of the record, likely needed the next time the
handler is called, would already be read in thus saving a disk access!)

cjl