JES: Just Educational Services

Inside Pick, Part 2: The Pick Computer
Toby L. Kraft

In Part 1 of this article (ed. note: We will have Part 1 up on the Net as soon as we can!) we discussed the memory model of the Pick Virtual Machine (PVM). Now let's have a look at how the Pick "CPU" works and how it accesses the virtual memory. Remember this is still just an illusion created by the underlying firmware / monitor / native code environment implemented by the system supplier.

The Pick CPU

The Pick CPU implements a complement of instructions specifically tailored for the virtual memory, string oriented environment of the Pick system. A complete discussion of the instruction set is beyond the scope of this column. If you happen to have an assembler reference manual for any of the systems, this discussion should help you make sense out of the manual.

The Pick CPU has 16 address registers that are used to reference memory. Three of these registers are dedicated for special purposes. Register 0 always points to the Process Control Block (PCB) for the process. Register 1 always points to the frame containing the object code currently being executed by the process. Register 2 always points to frame PCB+1. The other registers can point anywhere in virtual memory but most registers, by software convention, are used for specific purposes by the system software.

The memory operands on which the instruction set operates include bits, bytes (signed), character, word (2bytes called a tally), double word (4bytes called a double tally), and triple word (6bytes called a triple tally). All memory operands are addressed using an address register. The operand encoding includes the address register, offset, and memory element size. When the operand is a character, only a register need be specified.

The address registers themselves can be operands for certain instructions in which case the address contained in the register is manipulated. Registers never contain data (unless it is an address), only addresses that may point to a location in virtual memory that contains the data of interest.

Process Control Block

The PCB and PCB+1 frames contain a collection of storage elements that the system software uses to store information and pass parameters between software modules (called modes in Pick vernacular). The frames were carved up into various elements, each element given a name, and a symbol table built up. The symbol table includes the register, offset, and element type (which implies size) for each named element in the PCB frames. The assembler looks up the elements and uses the info in the symbol table to build the proper instruction coding. Elements in the PCB are referenced via R0 and likewise, PCB+1 elements are referenced via R2.

A portion of the PCB is used to store the 16 address registers when the process is inactive. When the process is active, the exact operation of the address registers is dependent on the hardware and native code implementation. For example, in the PC, DS:SI and ES:DI are dynamically loaded from the PCB registers as required to access the memory. You can think of the PCB as storing the state of each process. R1 in this case points to the next instruction to execute when the process is reactivated. You can also think of the Process WorkSpace (PWS) including the PCB as your own private computer with your own 16 registers, some of which point to your local storage (your private workspace) and some of which point to shared memory (the file space).

Process Work Space

Another point to make here is that all virtual software uses the PWS for its local storage. This means that there is no explicit parameter passing when calling system routines and there is no data stack. For example, to open a file, you would set a register pointing to the file name in a string storage area in your PWS and then call the FILEOPEN routine which would put the file's Base, Modulo, and Separation into elements called BASE and MODSEP in your PCB. A bit in the PCB would be set to indicate success or failure.

This also means that all the system software is re-entrant and the code is shared among all users. Any number of users can be simultaneously executing the FILEOPEN routine since FILEOPEN only manipulates each user's private workspace.

Instructions

To give you a feel for the instruction set, I'll discuss a couple of them in detail.

MOV D4,D5
The contents of PCB element D4 are stored in element D5.

INC R15,T0
The address in R15 is incremented by the value stored in T0. This changes the byte address of R15 +-32K. When R15 is used to access the byte in storage, the address is normalized to the correct frame and displacement, following any fwd/bck links as required to reference the requested byte location.

MCC SM,R15
Store a segment mark character at the location pointed to by R15.

MIID R14,R15,X'C0'
Copy the character string pointed to by R14 to the location pointed to by R15 until a segment mark is encountered in the source string. This single instruction will copy any number of bytes, following forward links as required in the source and destination linked frame sets, and can be set to cause the destination frame set to grow dynamically.

BLEZ T0,LABEL
Branch to LABEL if the value of T0 is less than or equal to zero.

BBS ZFLG,LABEL
Branch to LABEL if the bit ZFLG is set.

READ R15
Read a character from the terminal input queue and store it in the byte location addressed by R15. If the queue is empty, the process is suspended until data is received. Note that there is no port number in this instruction. This is why communications has been, shall we say, awkward in Pick.

Address Registers

The 16 address registers are used for all memory accesses. Let's look at how this is accomplished. When the process is active, the address is cached in hardware registers (very implementation specific). When the process deactivates, the hardware registers must be reused, or certain instructions affect the address register directly, the address is stored back into the PCB.

The Address Register storage elements in the PCB are each eight bytes in size. The structure of the 8 bytes is:

     | address  | displacement | flag | frameid |
     |   2      |     2        |  1   |    3    |

For you C aficionados,

     struc AR {
      unsigned int address;
      int displacement;
      unsigned char flag;
      unsigned char frameid[3]; /* a three byte thing */
     };

The address field is strictly implementation dependent. The old firmware machines stored the actual 16bit memory address in here (there was only 64K of memory). The displacement field contains a 16bit displacement from the beginning of the frame to the byte referenced. The flag byte contains a bit indicating whether the register is accessing the frame in linked or unlinked mode. And frameid contains the actual frame number.

So you are saying to yourself, what if the displacement is larger than the frame size? The answer is that the system will normalize the address to be within a frame when the register is used to reference data. If the frame is linked, the monitor code will follow the frame forward link fields, reading in the intervening frames as required to locate the proper frame, get it into memory, and access the required byte(s). Notice that the link bit in the AR indicates whether the frame is treated as linked or not. There is nothing in the frame itself that marks it as linked or not. If the AR is in unlinked format, the frame is assumed to be unlinked and the displacement can only be <= frame size. Setting up a register to a particular frame in unlinked format allows you to manipulate the link fields of the frame. In linked format, the only data that can be referenced is the data area of the frame, the link fields can not be addressed.

Linked and Unlinked Frames

This is an appropriate place to delve into linked frame sets. When a frame is linked, it is logically connected to one or more frames before it or after it. Each linked frame contains at least 12 bytes of link field data. The link fields are:

     | unused | NNCF | forward | backward | NPCF | unused |
     |   1    |  1   |    4    |    4     |  1   |   1    |

The forward link is the fid of the 'next' frame, the backward link is the fid of the 'previous' frame. The linked frames may be anywhere in virtual memory.

If the frames are contiguous, meaning physically next to each other, the link fields can be set up to indicate that the frames are contiguous (as opposed to anywhere). In this case, NNCF and NPCF are non-zero and are used to indicate how many contiguous frames are to the left and right of the current frame. NNCF is therefore: Number of Next Contiguous Frames and NPCF is Number of Previous Contiguous Frames. A contiguously linked set might have link fields like:

                      NNCF  FWD     BCK   NPCF
      FID:   174826 :   4  174827       0   0
    + FID:   174827 :   3  174828  174826   1
    + FID:   174828 :   2  174829  174827   2
    + FID:   174829 :   1  174830  174828   3
    + FID:   174830 :   0       0  174829   4

What is this good for? When the monitor is normalizing an address to reference a byte of data somewhere, if the frames are contiguously linked, the intervening frames do not need to be read into memory to follow the links. The required frameid can be computed from the current fid and the displacement.

For example, if you have a register (say R15) pointing to byte 1 of (512 byte) frame 174826 and you do an INC R15,2000, the monitor can simply calculate that fid 174830 is the frame it needs and read it in. Frames 174827, 28, and 29 are not read into memory.

This is real useful for things that want to be addressed via an offset from some base location. Things like Basic object code. The run-time uses a 16bit offset for the goto/gosub instructions which limits the jumps to 32K. Also things like the Basic descriptor table in your PWS. The run-time uses a 16bit offset to address variables in the table; each variable is a 10 byte descriptor; the table can only be 32K in size (the maximum offset); therefore the maximum number of variables is 32768/10 = 3276. The default select list and file variable use 2 so a program can have a maximum of 3274 variables.

Summary

I've tried to present an introduction to the nature of the Pick machine. An understanding of this nature is important in understanding why things work the way they do when programming in Pick. Next time, we'll take a look at the internals of Basic and see where that leads us.

This article was originally published in The DataBase Digest Magazine.